React: [ESLint] Commentaires pour la règle de charpie 'exhaustive-deps'

Créé le 21 févr. 2019  ·  111Commentaires  ·  Source: facebook/react

Réponses courantes

??

Nous avons analysé les commentaires sur cet article pour fournir quelques conseils : https://github.com/facebook/react/issues/14920#issuecomment -471070149.

??


Qu'est-ce que c'est

Il s'agit d'une nouvelle règle ESLint qui vérifie la liste des dépendances pour les Hooks comme useEffect et similaires, protégeant ainsi contre les pièges de la fermeture obsolète. Dans la plupart des cas, il a un autofix. Nous ajouterons plus de documentation au cours des prochaines semaines.

autofix demo

Installation

yarn add eslint-plugin-react-hooks<strong i="18">@next</strong>
# or
npm install eslint-plugin-react-hooks<strong i="19">@next</strong>

Configuration ESLint :

{
  "plugins": ["react-hooks"],
  // ...
  "rules": {
    "react-hooks/rules-of-hooks": 'error',
    "react-hooks/exhaustive-deps": 'warn' // <--- THIS IS THE NEW RULE
  }
}

Cas de test simple pour vérifier que la règle fonctionne :

function Foo(props) {
  useEffect(() => {
    console.log(props.name);
  }, []); // <-- should error and offer autofix to [props.name]
}

La règle des peluches se plaint mais mon code va bien !

Si cette nouvelle règle de lint react-hooks/exhaustive-deps déclenche pour vous mais que vous pensez que votre code est correct , veuillez le publier dans ce numéro.


AVANT DE POSTER UN COMMENTAIRE

Veuillez inclure ces trois éléments :

  1. Un CodeSandbox démontrant un exemple de code minimal qui exprime toujours votre intention (pas "foo bar" mais le modèle d'interface utilisateur réel que vous implémentez).
  2. Une explication des étapes effectuées par un utilisateur et de ce que vous vous attendez à voir à l'écran.
  3. Une explication de l' API prévue de votre Hook/composant.

please

Mais mon cas est simple, je ne veux pas inclure ces choses-là !

C'est peut-être simple pour vous, mais ce n'est pas du tout simple pour nous. Si votre commentaire n'inclut aucun des deux (par exemple, aucun lien CodeSandbox), nous masquerons votre commentaire car il est très difficile de suivre la discussion autrement. Merci de respecter le temps de chacun en les incluant.

L'objectif final de ce fil est de trouver des scénarios courants et de les transformer en de meilleurs documents et avertissements. Cela ne peut se produire que lorsque suffisamment de détails sont disponibles. Les commentaires intempestifs avec des extraits de code incomplets réduisent considérablement la qualité de la discussion, au point que cela n'en vaut pas la peine.

ESLint Rules Discussion

Commentaire le plus utile

Nous avons fait une passe dessus avec @threepointone aujourd'hui. Voici un résumé :

Corrigé dans la règle Lint

Dépendances useEffect étrangères

La règle ne vous empêche plus d'ajouter des deps "étrangers" à useEffect car il existe des scénarios légitimes.

Fonctions dans le même composant mais définies en dehors de l'effet

Linter n'avertit pas des cas où il est sûr maintenant, mais dans tous les autres cas, il vous donne de meilleures suggestions (comme déplacer la fonction à l'intérieur de l'effet ou l'envelopper avec useCallback ).

Vaut la peine d'être corrigé dans le code utilisateur

Réinitialisation de l'état sur le changement d'accessoires

Cela ne produit plus de violations de peluches, mais la manière idiomatique de réinitialiser l'état en réponse aux accessoires est différente . Cette solution aura un rendu supplémentaire incohérent, donc je ne suis pas sûr que ce soit souhaitable.

"Ma valeur de non-fonction est constante"

Les crochets vous poussent vers l'exactitude dans la mesure du possible. Si vous spécifiez les DEPS (qui , dans certains cas , vous pouvez omettre), nous vous recommandons fortement d'inclure même ceux que vous pensez ne changera pas. Oui, dans cet exemple de useDebounce est peu probable que le délai change. Mais c'est toujours un bug si c'est le cas, mais le Hook ne peut pas le gérer. Cela apparaît également dans d'autres scénarios. (Par exemple, les crochets sont beaucoup plus compatibles avec le rechargement à chaud car chaque valeur est traitée comme dynamique.)

Si vous insistez absolument sur le fait qu'une certaine valeur est statique, vous pouvez l'appliquer.
Le moyen le plus sûr est de le faire explicitement dans votre API :

const useFetch = createFetch({ /* config object */});
const useDebounce = createDebounce(500);
const FormInput = createInput({ rules: [emailValidator, phoneValidator] });

Ensuite, il ne peut <Slider min={50} /> ne peut jamais changer n'est pas vraiment valable — quelqu'un pourrait facilement le changer en <Slider min={state ? 50 : 100} /> . En fait, quelqu'un pourrait faire ça :

let slider
if (isCelsius) {
  slider = <Slider min={0} max={100} />
} else {
  slider = <Slider min={32} max={212} />
}

Si quelqu'un change d'état isCelsius , un composant supposant que min ne change jamais ne pourra pas se mettre à jour. Il n'est pas évident dans ce cas que le Slider sera le même (mais ce sera parce qu'il a la même position dans l'arbre). Il s'agit donc d'une arme de poing majeure en termes de modifications du code. Un point majeur de React est que les mises à jour sont rendues exactement comme les états initiaux (vous ne pouvez généralement pas dire lequel est lequel). Que vous rendiez la valeur de prop B ou que vous passiez de la valeur de prop A à B, cela devrait ressembler et se comporter de la même manière.

Bien que cela soit déconseillé, dans certains cas, le mécanisme d'application peut être un Hook qui avertit lorsque la valeur change (mais fournit la première). Au moins alors il est plus susceptible d'être remarqué.

function useMyHook(a) {
  const initialA = usePossiblyStaleValue(a);
  // ...
}

function usePossiblyStaleValue(value) {
  const ref = useRef(value);

  if (process.env.NODE_ENV !== 'production') {
    if (ref.current !== value) { // Or your custom comparison logic
      console.error(
        'Unlike normally in React, it is not supported ' +
        'to pass dynamic values to useMyHook(). Sorry!'
      );
    }
  }

  return ref.current;
}

Il peut également y avoir un cas légitime où vous ne pouvez tout simplement key sur les mises à jour incompatibles, forçant un remontage propre avec de nouveaux accessoires. C'est probablement préférable pour les composants feuilles comme les curseurs ou les cases à cocher.

"Ma valeur de fonction est constante"

Tout d'abord, s'il est constant et hissé au plus haut niveau, le linter ne se plaindra pas. Mais cela n'aide pas avec les choses provenant des accessoires ou du contexte.

S'il est vraiment constant, le spécifier dans deps ne fait pas de mal. Comme dans le cas où une fonction setState l'intérieur d'un Hook personnalisé est renvoyée à votre composant, puis vous l'appelez à partir d'un effet. La règle des peluches n'est pas assez intelligente pour comprendre une telle indirection. Mais d'un autre côté, n'importe qui peut encapsuler ce rappel plus tard avant de revenir, et éventuellement référencer un autre accessoire ou état à l'intérieur. Alors ce ne sera pas constant ! Et si vous ne parvenez pas à gérer ces changements, vous aurez de vilains bogues de prop/state périmés. Donc, le spécifier est une meilleure valeur par défaut.

Cependant, c'est une idée fausse que les valeurs des fonctions sont nécessairement constantes. Ils sont plus souvent constants dans les classes en raison de la liaison de méthode, bien que cela crée sa propre gamme de bogues . Mais en général, toute fonction qui se ferme sur une valeur dans un composant de fonction ne peut pas être considérée comme constante. La règle des peluches est maintenant plus intelligente pour vous dire quoi faire. (Comme le déplacer à l' useCallback .)

Il y a un problème sur le spectre opposé de cela, c'est là que vous obtenez des boucles infinies (une valeur de fonction change toujours). Nous attrapons cela dans la règle des peluches maintenant lorsque cela est possible (dans le même composant) et suggérons un correctif. Mais c'est délicat si vous passez quelque chose de plusieurs niveaux vers le bas.

Vous pouvez toujours l'envelopper dans useCallback pour résoudre le problème. N'oubliez pas que techniquement, il est valide qu'une fonction change , et vous ne pouvez pas ignorer ce cas sans risquer des bogues. Comme onChange={shouldHandle ? handleChange : null} ou le rendu de foo ? <Page fetch={fetchComments /> : <Page fetch={fetchArticles /> au même endroit. Ou même fetchComments qui se ferme sur l'état du composant parent. Cela peut changer. Avec les classes, son comportement changera silencieusement mais la référence de fonction restera la même. Ainsi, votre enfant manquera cette mise à jour - vous n'avez pas vraiment d'autre choix que de transmettre plus de données à l'enfant. Avec les composants de fonction et useCallback , l'identité de la fonction elle-même change, mais uniquement lorsque cela est nécessaire. C'est donc une propriété utile et pas seulement un obstacle à éviter.

Nous devrions ajouter une meilleure solution pour détecter les boucles asynchrones infinies. Cela devrait atténuer l'aspect le plus déroutant. Nous pourrions ajouter une détection pour cela à l'avenir. Vous pouvez également écrire quelque chose comme ceci vous-même :

useWarnAboutTooFrequentChanges([deps]);

Ce n'est pas idéal et nous devrons réfléchir davantage à la manière de gérer cela avec élégance. Je suis d'accord que les cas comme celui-ci sont assez désagréables. Le correctif sans enfreindre la règle serait de rendre rules statique, par exemple en changeant l'API en createTextInput(rules) , et d'envelopper register et unregister dans useCallback . Mieux encore, supprimez register et unregister , et remplacez-les par un contexte séparé où vous mettez dispatch seul. Ensuite, vous pouvez garantir que vous n'aurez jamais une identité de fonction différente de la lecture.

J'ajouterais que vous voudrez probablement mettre useMemo sur la valeur de contexte de toute façon parce que le fournisseur fait beaucoup de calculs qu'il serait triste de répéter si aucun nouveau composant n'était enregistré mais que son propre parent était mis à jour. Donc, ce genre de problème rend le problème que vous n'auriez peut-être pas remarqué autrement plus visible. Bien que je convienne que nous devons le rendre encore plus important lorsque cela se produit.

Ignorer complètement les dépendances de fonction conduit à de pires bogues avec les composants de fonction et les Hooks, car ils continueraient à voir des accessoires et des états périmés si vous le faites. Alors essayez de ne pas le faire, quand vous le pouvez.

Réagir aux changements de valeur composée

C'est étrange pour moi pourquoi cet exemple utilise un effet pour quelque chose qui est essentiellement un gestionnaire d'événements. Faire le même "journal" (je suppose que cela pourrait être une soumission de formulaire) dans un gestionnaire d'événements semble plus approprié. Cela est particulièrement vrai si nous pensons à ce qui se passe lorsque le composant se démonte. Que se passe-t-il s'il se démonte juste après la programmation de l'effet ? Des choses comme la soumission de formulaire ne devraient pas simplement « ne pas arriver » dans ce cas. Il semble donc que l'effet pourrait être un mauvais choix là-bas.

Cela dit, vous pouvez toujours faire ce que vous avez essayé - en faisant en sorte que fullName soit setSubmittedData({firstName, lastName}) place, puis [submittedData] est votre dépendance, à partir de laquelle vous pouvez lire firstName et lastName .

Intégration avec le code impératif/ancien

Lors de l'intégration avec des éléments impératifs tels que des plugins jQuery ou des API DOM brutes, une certaine méchanceté peut être attendue. Cela dit, je m'attendrais toujours à ce que vous puissiez consolider un peu plus les effets dans cet exemple.


J'espère que je n'ai oublié personne ! Faites-moi savoir si je l'ai fait ou si quelque chose n'est pas clair. Nous allons essayer de transformer les leçons de cela en quelques documents bientôt.

Tous les 111 commentaires

Cet exemple a une réponse : https://github.com/facebook/react/issues/14920#issuecomment -466145690

CodeSandbox

Il s'agit d'un composant Checkbox non contrôlé qui prend un accessoire defaultIndeterminate pour définir le statut indéterminé lors du rendu initial (ce qui ne peut être fait qu'en JS en utilisant une référence car il n'y a pas d'attribut d'élément indeterminate ). Cette prop est destinée à se comporter comme defaultValue , où sa valeur n'est utilisée que lors du rendu initial.

La règle se plaint que defaultIndeterminate est manquant dans le tableau de dépendances, mais l'ajouter entraînerait le remplacement incorrect par le composant de l'état non contrôlé si une nouvelle valeur est transmise. Le tableau de dépendances ne peut pas être entièrement supprimé car cela entraînerait le contrôle total du statut indéterminé par le prop.

Je ne vois aucun moyen de faire la distinction entre cela et le type de cas que la règle est censée attraper, mais ce serait formidable si la documentation finale de la règle pouvait inclure une solution de contournement suggérée. ??

Re : https://github.com/facebook/react/issues/14920#issuecomment -466144466

@billyjanitsch Cela fonctionnerait-il à la place ? https://codesandbox.io/s/jpx1pmy7ry

J'ai ajouté useState pour indeterminate qui est initialisé à defaultIndeterminate . L'effet accepte alors [indeterminate] comme argument. Vous ne le changez pas actuellement - mais si vous le faisiez plus tard, je suppose que cela fonctionnerait aussi? Ainsi, le code anticipe un peu mieux les futurs cas d'utilisation possibles.

J'ai donc le cas suivant (bord?) où je passe du code html et l'utilise avec dangerouslySetInnerHtml pour mettre à jour mon composant (du contenu éditorial).
Je n'utilise pas le prop html mais la référence où je peux utiliser ref.current.querySelectorAll pour faire de la magie.
Maintenant, je dois ajouter html à mes dépendances dans useEffect même si je ne l'utilise pas explicitement. Est-ce un cas d'utilisation où je devrais réellement désactiver la règle ?

L'idée est d'intercepter tous les clics sur les liens du contenu éditorial et de suivre certaines analyses ou de faire d'autres choses pertinentes.

Ceci est un exemple édulcoré d'un composant réel :
https://codesandbox.io/s/8njp0pm8v2

J'utilise react-redux, donc lorsque je transmets un créateur d'action dans les accessoires de mapDispatchToProps et que j'utilise ce créateur d'action dans un hook, je reçois un avertissement exhaustive-deps .

Je peux donc évidemment ajouter l'action redux au tableau de dépendances, mais parce que l'action redux est une fonction et ne change jamais, c'est inutile, n'est-ce pas ?

const onSubmit = React.useCallback(
  () => {
    props.onSubmit(emails);
  },
  [emails, props]
);

Je m'attends à ce que la charpie répare les deps dans [emails, props.onSubmit] , mais pour le moment, elle répare toujours les deps dans [emails, props] .

  1. Un CodeSandbox démontrant un exemple de code minimal qui exprime toujours votre intention (pas "foo bar" mais le modèle d'interface utilisateur réel que vous implémentez).

https://codesandbox.io/s/xpr69pllmz

  1. Une explication des étapes effectuées par un utilisateur et de ce que vous vous attendez à voir à l'écran.

Un utilisateur ajoutera un e-mail et invitera ces e-mails à l'application. Je supprime intentionnellement le reste de l'interface utilisateur uniquement pour les button car ils ne sont pas pertinents pour mon problème.

  1. Une explication de l' API prévue de votre Hook/composant.

Mon composant a un seul accessoire, onSubmit: (emails: string[]) => void . Il sera appelé avec l'état emails lorsqu'un utilisateur soumettra le formulaire.


EDIT: répondu dans https://github.com/facebook/react/issues/14920#issuecomment -467494468

C'est parce que techniquement props.foo() passe props lui-même en tant this à foo . Ainsi, foo pourrait implicitement dépendre de props . Nous aurons besoin d'un meilleur message pour cette affaire cependant. La meilleure pratique est toujours déstructurante.

CodeSandbox

Il ne considère pas que le montage et la mise à jour peuvent être nettement différents lors de l'intégration de bibliothèques tierces. L'effet de mise à jour ne peut pas être inclus dans le montage (et supprimer complètement le tableau), car l'instance ne doit pas être détruite à chaque rendu

Salut,

Je ne sais pas ce qui ne va pas avec mon code ici :

const [client, setClient] = useState(0);

useEffect(() => {
    getClient().then(client => setClient(client));
  }, ['client']);

J'ai React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked

@joelmoss @sylvainbaronnet J'apprécie vos commentaires mais j'ai caché vos commentaires car ils n'incluaient pas les informations que nous avions demandées en haut du numéro. Cela rend cette discussion inutilement difficile pour tout le monde parce qu'il manque un contexte. Je serais heureux de poursuivre la conversation si vous postez à nouveau et incluez toutes les informations pertinentes (voir le post du haut). Merci de votre compréhension.

  1. CodeSandbox
  2. L'utilisateur peut sélectionner un prénom et il force le nom à changer. L'utilisateur peut sélectionner un nom de famille et cela ne force pas le changement de prénom.
  3. Il existe un hook personnalisé qui renvoie un élément d'état, un composant select qui met à jour cet état et la méthode de mise à jour du hook d'état qui met à jour l'état par programmation. Comme démontré, je ne veux pas toujours utiliser la fonction de mise à jour, donc je l'ai laissé au dernier élément du tableau renvoyé.

Je crois que le code tel quel ne devrait pas générer d'erreur. Il le fait maintenant à la ligne 35, en disant que setLastName doit être inclus dans le tableau. Des idées sur ce qu'il faut faire à ce sujet? Est-ce que je fais quelque chose d'inattendu ?

Je comprends tout à fait, et je ferais normalement tout cela pour vous, mais dans mon cas particulier, aucun code ne m'est unique. Il s'agit simplement de savoir si l'utilisation d'une fonction définie en dehors du hook (c'est-à-dire un créateur d'action redux) et utilisée dans un hook devrait nécessiter l'ajout de cette fonction en tant que hook dep.

Heureux de créer une codesandbox si vous avez encore besoin de plus d'informations. THX

@joelmoss Je pense que ma reproduction couvre également votre cas.

Oui, un CodeSandbox serait toujours utile. S'il vous plaît, imaginez ce que c'est que de changer de contexte entre les extraits de code des gens toute la journée. C'est un lourd tribut mental. Aucun d'eux ne se ressemble. Lorsque vous devez vous rappeler comment les gens utilisent les créateurs d'action dans Redux ou un autre concept externe à React, c'est encore plus difficile. Le problème peut vous sembler évident mais ce que vous vouliez dire n'est pas du tout évident pour moi.

@gaearon Je comprends que cela a du sens, en fait cela a fonctionné pour moi parce que la raison pour laquelle je voulais atteindre était :

Si vous souhaitez exécuter un effet et le nettoyer une seule fois (au montage et au démontage), vous pouvez passer un tableau vide ([]) comme deuxième argument.

Merci beaucoup pour l'éclaircissement. (et désolé pour le hors sujet)

Voici la version du code de ce que j'implémente. Cela ne ressemble pas à grand-chose, mais le modèle est 100% identique à mon vrai code.

https://codesandbox.io/s/2x4q9rzwmp?fontsize=14

  • Une explication des étapes effectuées par un utilisateur et de ce que vous vous attendez à voir à l'écran.

Tout fonctionne très bien dans cet exemple ! Sauf le problème de linter.

  • Une explication de l' API prévue de votre Hook/composant.

J'ai plusieurs fichiers comme celui-ci, où j'exécute une fonction dans l'effet, mais je veux seulement qu'elle s'exécute chaque fois qu'une condition est remplie - par exemple, changer l'ID de série. Je ne veux pas inclure la fonction dans le tableau.

Voici ce que je reçois du linter lorsque je l'exécute sur le code du bac à sable :

25:5   warning  React Hook useEffect has a missing dependency: 'fetchPodcastsFn'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

Ma question est : est-ce ainsi qu'il devrait se comporter (soit la règle linter, soit la règle hook array) ? Existe-t-il une manière plus idiomatique de décrire cet effet ?

@svenanders , je suis curieux de savoir pourquoi vous ne voulez pas inclure fetchPodcastsFn ? Est-ce parce que vous trouvez que cela change à chaque rendu ? Si c'est le cas, vous voudrez probablement mémoriser cette fonction ou la rendre statique (au cas où elle n'aurait aucun paramètre)

D'une part, il s'agit de clarté. Quand je regarde la fonction, je veux comprendre facilement quand elle doit se déclencher. Si je vois _one_ id dans le tableau, c'est clair. Si je vois cet identifiant et un tas de fonctions, cela devient plus confus. Je dois consacrer du temps et des efforts, peut-être même à des fonctions de débogage, pour comprendre ce qui se passe.
Mes fonctions ne changent pas au moment de l'exécution, donc je ne sais pas si les mémoriser serait important (celle-ci en particulier est une action de répartition qui déclenche une épopée, entraînant éventuellement un changement d'état).

https://codesandbox.io/s/4xym4yn9kx

  • Pas

L'utilisateur accède à un itinéraire sur la page, mais ce n'est pas un super utilisateur, nous souhaitons donc le rediriger hors de la page. Le props.navigate est injecté via une bibliothèque de routeur donc nous ne voulons pas réellement utiliser window.location.assign pour empêcher un rechargement de page.

Tout fonctionne!

  • API prévue

J'ai mis correctement les dépendances comme dans le sandbox du code, mais le linter me dit que la liste des dépendances devrait avoir props au lieu de props.navigate . Pourquoi donc?

Un tweet avec des captures d'écran ! https://twitter.com/ferdaber/status/1098966716187582464

EDIT: une raison valable pour laquelle cela pourrait être bogué est si navigate() est une méthode qui repose sur un this lié, auquel cas techniquement si quelque chose à l'intérieur de props change alors les choses à l'intérieur this changera également.

CodeSandbox : https://codesandbox.io/s/711r1zmq50

API prévue :

Ce crochet vous permet de debounce n'importe quelle valeur changeant rapidement. La valeur anti-rebond ne reflétera la dernière valeur que lorsque le hook useDebounce n'a pas été appelé pendant la période spécifiée. Lorsqu'il est utilisé avec useEffect, comme nous le faisons dans la recette, vous pouvez facilement vous assurer que les opérations coûteuses telles que les appels d'API ne sont pas exécutées trop fréquemment.

Pas:

L'exemple vous permet de rechercher l'API Marvel Comic et utilise useDebounce pour empêcher les appels d'API d'être déclenchés à chaque frappe.

L'OMI ajoutant "tout" que nous utilisons dans le tableau de dépendances n'est pas efficace.

Par exemple, considérons le crochet useDebounce . Dans les usages du monde réel (au moins dans le nôtre), le delay ne changera pas après le premier rendu, mais nous vérifions s'il a changé ou non à chaque nouveau rendu. Ainsi, dans ce hook, le deuxième argument de useEffect vaut mieux [value] au lieu de [value, delay] .

S'il vous plaît, ne pensez pas que les contrôles d'égalité superficiels sont extrêmement bon marché. Ils peuvent aider votre application lorsqu'ils sont placés de manière stratégique, mais le simple fait de rendre pur chaque composant peut en fait ralentir votre application. Compromis.

Je pense que l'ajout de tout dans le tableau des dépendances a le même (ou même pire) problème de performances que l'utilisation de composants purs partout. Depuis, il existe de nombreux scénarios dans lesquels nous savons que certaines des valeurs que nous utilisons ne changeront pas, nous ne devrions donc pas les ajouter dans le tableau des dépendances, car comme l' a dit

J'ai des commentaires sur l'activation de cette règle pour qu'elle fonctionne automatiquement sur les crochets personnalisés par convention.

Je peux voir dans le code source qu'il y a une certaine intention d'autoriser les gens à spécifier une expression régulière pour capturer les crochets personnalisés par nom :

https://github.com/facebook/react/blob/ba708fa79b3db6481b7886f9fdb6bb776d0c2fb9/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js#L490 -L492

Que penserait l'équipe React d'une convention de nommage supplémentaire pour les hooks personnalisés avec des tableaux de dépendances ? Les hooks suivent déjà la convention d'être préfixés par use afin d'être détectés comme hook par ce plugin. La convention que je proposerais pour détecter les crochets personnalisés qui reposent sur certaines dépendances serait une sorte de suffixe, quelque chose comme WithDeps , ce qui signifie qu'un nom de crochet personnalisé complet pourrait être quelque chose comme useCustomHookWithDeps . Le suffixe WithDeps indiquerait au plugin que l'argument final du tableau est l'un des dépendances.

Le plugin pourrait toujours prendre en charge les regex, mais je pense que je verrais un grand avantage en permettant aux auteurs de bibliothèques d'exporter simplement leurs crochets personnalisés postfixés avec WithDeps plutôt que de forcer les consommateurs de bibliothèque à configurer explicitement le plugin pour tout et tout personnalisé crochets tiers ou autre.

Il avertit et supprime automatiquement le contrôle d'égalité personnalisé.

const myEqualityCheck = a => a.includes('@');

useCallback(
  () => {
    // ...
  },
  [myEqualityCheck(a)]
);

Pour useEffect, je ne pense pas que l'avertissement "dépendance inutile" devrait apparaître car ces "dépendances" changeront la façon dont l'effet se déclenche.

Disons que j'ai deux compteurs, parent et enfant :

  • Les compteurs peuvent être incrémentés/décrémentés indépendamment.
  • Je souhaite remettre le compteur enfant à zéro lorsque le compteur parent change.

Mise en œuvre:

  const [parent, setParent] = useState(0);
  const [child, setChild] = useState(0);

  useEffect(
    () => {
      setChild(0);
    },
    [parent] // "unnecessary dependency: 'parent'"
  );

La règle exhaustive-deps donne l'avertissement React Hook useEffect has an unnecessary dependency: 'parent'. Either exclude it or remove the dependency array.

screen shot 2019-02-25 at 11 06 40 am

Je ne pense pas que le linter devrait faire cette suggestion car cela change le comportement de l'application.

  1. CodeSandbox : https://codesandbox.io/s/ol6r9plkv5
  2. Étapes : Lorsque parent change, child remis à zéro.
  3. Intention : Au lieu de signaler la dépendance comme inutile, le linter devrait reconnaître que parent modifie le comportement de useEffect et ne devrait pas me conseiller de la supprimer.

[ÉDITER]

Pour contourner le problème, je peux écrire quelque chose comme

const [parent, setParent] = useState(0)
const [child, setChild] = useState(0)
const previousParent = useRef(parent)

useEffect(() => {
  if (parent !== previousParent.current) {
    setChild(0)
  }

  previousParent.current = parent
}, [parent])

Mais il y a une "poésie" à l'extrait original que j'aime bien.

Je pense que la question se résume à savoir si nous devons interpréter le tableau de dépendances comme un simple mécanisme d'optimisation des performances par rapport au comportement réel des composants. REACT crochets FAQ utilise la performance comme un exemple pour pourquoi vous l' utiliser, mais je n'interprétait l'exemple signifie que vous ne devez utiliser le tableau de dépendance pour améliorer les performances, au lieu que je l'ai vu comme un moyen pratique d'ignorer les appels à effet en général.

C'est en fait un modèle que j'ai utilisé plusieurs fois pour invalider un cache interne :

useEffect(() => {
  if (props.invalidateCache) {
    cache.clear()
  }
}, [props.invalidateCache])

Si je ne dois pas l'utiliser de cette manière, n'hésitez pas à me le faire savoir et à ne pas tenir compte de ce commentaire.

@MrLeebo
Qu'en est-il de

  const [parent, setParent] = useState(0);
  const [child, setChild] = useState(0);
  const updateChild = React.useCallback(() => setChild(0), [parent]);

  useEffect(
    () => {
      updateChild();
    },
    [updateChild] 
  );

Je ne comprendrais pas non plus cet extrait de toi. La dépendance ne peut être supposée qu'en fonction de votre code, mais il peut s'agir d'un bogue. Bien que ma proposition ne résolve pas nécessairement cela, elle la rend au moins plus explicite.

Il semble que cela ait déjà été résolu via getDerivedStateFromProps ? Comment puis-je implémenter getDerivedStateFromProps ? aider?

@nghiepit J'ai caché votre commentaire car vous avez ignoré la liste de contrôle requise dans le premier message (par exemple, CodeSandbox). Veuillez suivre la liste de contrôle et publier à nouveau. Merci.

@eps1lon Vous auriez exactement le même avertissement sur useCallback , et je ne suis pas d'accord avec le fait que ce modèle soit en général une amélioration par rapport à l'original, mais je ne veux pas faire dérailler le sujet pour en parler. Pour clarifier, je pense que la règle de dépendance inutile devrait être assouplie pour useEffect ou useLayoutEffect particulier, car elles peuvent contenir une logique efficace, mais la règle devrait rester en place pour useCallback , useMemo , etc.

Je rencontre certains des mêmes problèmes/questions de modèle mental que @MrLeebo a décrits ici. Mon intuition est que la règle de dépendance useEffect ne peut pas être aussi stricte. J'ai un exemple incroyablement artificiel sur lequel je travaillais pour une idée de base de preuve de concept. Je sais que ce code est nul et que l'idée n'est pas particulièrement utile, mais je pense que c'est une bonne illustration du problème posé. Je pense que @MrLeebo a assez bien exprimé la question que j'ai :

Je pense que la question se résume à savoir si nous devons interpréter le tableau de dépendances comme un simple mécanisme d'optimisation des performances par rapport au comportement réel des composants. REACT crochets FAQ utilise la performance comme un exemple pour pourquoi vous l' utiliser, mais je n'interprétait l'exemple signifie que vous ne devez utiliser le tableau de dépendance pour améliorer les performances, au lieu que je l'ai vu comme un moyen pratique d'ignorer les appels à effet en général.

https://codesandbox.io/s/5v9w81j244

Je regarderais spécifiquement le crochet useEffect dans useDelayedItems . À l'heure actuelle, l'inclusion de la propriété items dans le tableau de dépendances provoquera une erreur de linting, mais la suppression de cette propriété ou du tableau vous donne entièrement un comportement que vous ne souhaitez pas .

L'idée de base du crochet useDelayedItems est donnée un tableau d'éléments et un objet de configuration (délai en ms, taille de page initiale), je recevrai initialement un sous-ensemble d'éléments en fonction de ma taille de page configurée. Une fois que config.delay sont passés, les éléments seront désormais l'ensemble complet des éléments. Lorsqu'il reçoit un nouvel ensemble d'éléments, le crochet doit réexécuter cette logique de « retard ». L'idée est une version très grossière et stupide du rendu différé de grandes listes.

Merci pour tout le travail sur ces règles, cela a été très utile lors du développement. Même si mon exemple est de qualité douteuse, j'espère qu'il donne un aperçu de la façon dont ces opérateurs peuvent être utilisés.

EDIT : J'ai ajouté quelques commentaires de clarification à l'intérieur du codeandbox autour de l'intention de comportement. J'espère que les informations sont suffisantes, mais faites-moi savoir si quelque chose est encore confus.

Qu'en est-il d'une valeur unique qui combine d'autres valeurs ?

Exemple : fullName est dérivé de firstName et lastName . Nous voulons déclencher l'effet uniquement lorsque fullName change (comme lorsque l'utilisateur clique sur "Enregistrer") mais voulons également accéder aux valeurs qu'il compose dans l'effet

Démo

Ajouter firstName ou lastName aux dépendances casserait les choses puisque nous voulons seulement exécuter l'effet après que fullName ait changé.

@aweary Je ne suis pas sûr de la valeur que vous obtenez de l'indirection de changement de prop useEffect . Il semble que votre onClick devrait gérer cet "effet".

https://codesandbox.io/s/0m4p3klpyw

En ce qui concerne les valeurs uniques qui combinent d'autres valeurs, useMemo sera probablement ce que vous voulez. La nature retardée du calcul dans votre exemple signifie qu'il ne correspond pas exactement à 1:1 avec votre comportement lié.

Je vais créer des liens codesandbox pour ces exemples et éditer ce post.

J'ai une règle extrêmement simple : si l'onglet actuel a changé, faites défiler vers le haut :
https://codesandbox.io/s/m4nzvryrxj

useEffect(() => {
    window.scrollTo(0, 0);
  }, [activeTab]);

Et j'ai React Hook useEffect has an unnecessary dependency: 'activeTab'. Either exclude it or remove the dependency array

De plus, lors de la migration de certains composants, je souhaite répliquer le comportement componentDidMount :

useEffect(() => {
    ...
  }, []);

cette règle se plaint durement à ce sujet. J'hésite à ajouter chaque dépendance au tableau useEffect.

Enfin, si vous déclarez une nouvelle fonction en ligne avant useEffect, comme ceci :
https://codesandbox.io/s/nr7wz8qp7l

const foo = () => {...};
useEffect(() => {
    foo();
  }, []);

Vous obtenez : React Hook useEffect has a missing dependency: 'foo'. Either include it or remove the dependency array
Je ne suis pas sûr de ce que je pense de l'ajout de foo à la liste des dépendances. Dans chaque rendu foo est une nouvelle fonction et useEffect serait toujours exécuté ?

@ksaldana1 mettre l'effet secondaire dans le gestionnaire d'événements n'est pas suffisant. Cela entraînerait l'effet secondaire avant que la mise à jour réelle ne soit validée, ce qui pourrait provoquer le déclenchement de l'effet secondaire plus souvent que vous ne le souhaitez.

Cela ne fonctionnera pas non plus lors de l'utilisation de useReducer car l'état mis à jour ne sera pas disponible dans le gestionnaire d'événements.

En ce qui concerne les valeurs uniques qui combinent d'autres valeurs, useMemo sera probablement ce que vous voulez.

Si cet exemple utilisait useMemo il se briserait car useMemo dériverait un nouveau fullName chaque fois que firstName ou lastName changeaient. Le comportement ici est que fullName n'est pas mis à jour tant que le bouton Enregistrer n'est pas cliqué.

@aweary Est-ce que quelque chose comme ça marcherait ? https://codesandbox.io/s/lxjm02j50m
Je suis très curieux de voir quelle est la mise en œuvre recommandée.

Je suis également curieux d'en savoir plus sur certaines choses que vous avez dites. Pouvez-vous m'indiquer plus d'informations à ce sujet?

Déclenchement et effet dans le gestionnaire :

cela provoquerait l'effet secondaire avant que la mise à jour réelle ne soit validée, ce qui pourrait provoquer le déclenchement de l'effet secondaire plus souvent que vous ne le souhaitez.

Utilisation de l'état useReducer dans le gestionnaire d'événements :

l'état mis à jour ne sera pas disponible.

Merci!

@bugzpodder n'a aucun rapport mais l'appel de défilement devrait être à l'intérieur de useLayoutEffect au lieu de useEffect . Actuellement, il y a un scintillement notable lors de la navigation vers le nouvel itinéraire

Je ne sais pas si c'est intentionnel ou non :

Lorsque vous appelez une fonction à partir de props, le linter suggère d'ajouter l'intégralité de l'objet props en tant que dépendance.

Version courte:

useEffect(function() {
  props.setLoading(true)
}, [props.setLoading])

// ⚠️ React Hook useEffect has a missing dependency: 'props'.

Version complète : Codesandbox

J'essaye à nouveau... mais plus simple cette fois ;)

Lorsque je transmets une fonction dans les accessoires, ou même de n'importe où, et que j'utilise cette fonction à l'intérieur d'un crochet, je reçois un avertissement exhaustive-deps .

Je peux donc évidemment ajouter la fonction au tableau de dépendances, mais parce que c'est une fonction et qu'elle ne change jamais, c'est inutile, n'est-ce pas ?

-> Bac à sable

J'espère que c'est tout ce dont vous avez besoin, mais j'ai simplement forgé le bac à sable de

THX.

Je peux donc évidemment ajouter la fonction au tableau de dépendances, mais parce que c'est une fonction et qu'elle ne change jamais, c'est inutile, n'est-ce pas ?

Ce n'est pas correct ; les fonctions peuvent changer tout le temps lorsque le parent effectue un nouveau rendu.

@siddharthkp

Lorsque vous appelez une fonction à partir de props, le linter suggère d'ajouter l'intégralité de l'objet props en tant que dépendance.

C'est parce que techniquement props.foo() passe props lui-même en tant this à foo . Ainsi, foo peut dépendre implicitement de props . Nous aurons besoin d'un meilleur message pour cette affaire cependant. La meilleure pratique est toujours déstructurante.

les fonctions peuvent changer tout le temps lorsque le parent effectue un nouveau rendu.

Bien sûr, mais si la fonction était définie ailleurs et non à partir d'un composant parent, cela ne changerait jamais.

Bien sûr, mais si la fonction était définie ailleurs et non à partir d'un composant parent, cela ne changerait jamais.

Si vous l'importez directement, la règle de lint ne vous demandera pas de l'ajouter aux deps d'effet. Seulement si c'est dans la portée de rendu. S'il se trouve dans la portée de rendu, la règle de lint ne sait pas d'où il vient. Même si ce n'est pas dynamique aujourd'hui, cela pourrait l'être demain lorsque quelqu'un changera le composant parent. Donc, le spécifier est la bonne valeur par défaut. Cela ne fait pas de mal de le spécifier s'il est statique de toute façon.

merci @gaearon

C'est parce que techniquement props.foo() passe props lui-même en tant this à foo . Ainsi, foo peut dépendre implicitement de props . Nous aurons besoin d'un meilleur message pour cette affaire cependant. La meilleure pratique est toujours déstructurante.

Cela répond aussi à ma question. Merci! ??

https://codesandbox.io/s/98z62jkyro

Je crée donc une bibliothèque pour gérer la validation des entrées en enregistrant toutes les entrées à l'aide d'une API exposée dans un contexte pour que chaque entrée s'enregistre. J'ai créé un crochet personnalisé appelé useRegister.

Ex:

 useEffect(
    () => {
      register(props.name, props.rules || []);
      console.log("register");
      return function cleanup() {
        console.log("cleanup");
        unregister(props.name);
      };
    },
    [props.name, props.rules, register, unregister]
  );

Lorsque vous forcez props.rules à faire partie des dépendances, cela semble se terminer dans une boucle de rendu infinie. Props.rules est un tableau de fonctions à enregistrer en tant que validateurs. Je n'ai détecté ce problème que lors de la fourniture de tableaux en tant que dépendances. Dans ma codesandbox, vous pouvez voir qu'il boucle en ouvrant la console.

Le simple fait d'avoir props.name comme dépendance le fait fonctionner comme prévu. L'application des dépendances modifiera, comme d'autres l'ont souligné, le comportement de l'application et, à cette occasion, les effets secondaires sont graves.

@bugzpodder

Re : https://github.com/facebook/react/issues/14920#issuecomment -467212561

useEffect(() => {
    window.scrollTo(0, 0);
  }, [activeTab]);

Cela semble être un cas légitime. Je vais assouplir l'avertissement pour autoriser les profondeurs superflues pour les effets uniquement. (Mais pas pour useMemo ou useCallback .)

De plus, lors de la migration de certains composants, je souhaite répliquer le comportement componentDidMount :
cette règle se plaint durement à ce sujet. J'hésite à ajouter chaque dépendance au tableau useEffect.

Désolé, vous n'avez pas ajouté d'exemple pour cela, nous avons donc perdu l'occasion d'en discuter. C'est exactement pourquoi la publication OP demande de spécifier un exemple d'interface utilisateur concret . Vous l'avez fait pour le premier point mais pas celui-ci. Je suis heureux d'en discuter lorsque vous ajoutez un exemple concret pour cela. Les détails en dépendent vraiment.

Enfin, si vous déclarez une nouvelle fonction en ligne avant useEffect, comme ceci : https://codesandbox.io/s/nr7wz8qp7l

Dans ce cas, la solution la plus simple consiste à déplacer doSomething dans l'effet. Ensuite, vous n'avez pas besoin de le déclarer. Alternativement, vous pouvez useCallback environ doSomething . Je suis prêt à assouplir la règle pour autoriser l'omission de fonctions qui n'utilisent que des deps déclarés. Mais cela peut prêter à confusion si vous avez une fonction qui appelle une autre fonction et que vous ajoutez un accessoire ou un état dans l'une de ces fonctions. Soudain, tous les effets qui l'utilisent de manière transitive doivent être mis à jour. Cela peut prêter à confusion.

Je ne sais pas s'il s'agit d'une demande de fonctionnalité pour une nouvelle règle, ou quelque chose qui pourrait être amélioré à propos de exhaustive-deps

import React from 'react'
import emitter from './thing'

export const Foo = () => {
  const onChange = () => {
    console.log('Thing changed')
  }

  React.useEffect(() => {
    emitter.on('change', onChange)
    return () => emitter.off('change', onChange)
  }, [onChange])

  return <div>Whatever</div>
}

Étant donné que la fonction onChange est créée à chaque rendu, l'argument useEffect hook [onChange] est redondant et pourrait tout aussi bien être supprimé :

   React.useEffect(() => {
     emitter.on('change', onChange)
     return () => emitter.off('change', onChange)
-  }, [onChange])
+  })

Le linter pourrait détecter cela et vous conseiller de supprimer l'argument du tableau.

Il y a eu des situations où j'ai maintenu une liste d'éléments de tableau, seulement pour me rendre compte qu'un ou plusieurs d'entre eux étaient en cours de création et invalider le crochet à chaque rendu de toute façon.

Je viens de publier [email protected] avec quelques correctifs et de meilleurs messages pour cette règle. Rien de révolutionnaire mais quelques cas devraient être résolus. Je regarderai le reste la semaine prochaine.

J'ai également posté la première étape possible pour omettre les deps de fonction "sûr" ici: https://github.com/facebook/react/pull/14996. (Voir les tests.) Si vous avez des idées sur des heuristiques utiles et sur la façon de guider les gens vers les bonnes solutions, veuillez commenter le PR.

@gaearon Excellente idée. Cela sera certainement utile pour avoir un meilleur style lors de l'utilisation des crochets 🙏

@gaearon Cela ne fonctionne toujours pas si l'action provient d'un accessoire. Considérez cet exemple :

image

Dans lequel setScrollTop est une action de redux.

Dans cet exemple du composant Slider , j'utilise useEffect pour attendre que le DOM soit disponible afin de pouvoir monter le composant noUiSlider. Je passe donc [sliderElement] pour m'assurer que la ref est disponible dans le DOM lorsque l'effet s'exécute. Nous rendons également nos composants sur le serveur, ce qui garantit également que le DOM est disponible avant le rendu. Les autres accessoires que j'utilise dans useEffect (c'est-à-dire min, max, onUpdate, etc.) sont des constantes et je ne vois donc pas la nécessité de les transmettre à l'effet.

screen shot 2019-03-02 at 5 17 09 pm


Voici l'effet vu dans codesandbox :

const { min, max, step } = props;
const sliderElement = useRef();

useEffect(() => {
  if (!sliderElement.current) {
    return;
  }

  const slider = sliderElement.current;
  noUiSlider.create(slider, {
    connect: true,
    start: [min, max],
    step,
    range: {
      min: [min],
      max: [max],
    },
  });

  if (props.onUpdate) {
    slider.noUiSlider.on('update', props.onUpdate);
  }

  if (props.onChange) {
    slider.noUiSlider.on('change', props.onChange);
  }
}, [sliderElement]);

@WebDeg-Brian Je ne peux pas vous aider sans une démo complète de CodeSandbox. Désolé. Voir le post du haut.

J'ai posté un peu sur l'idée fausse commune "les fonctions ne changent jamais":

https://overreacted.io/how-are-function-components-different-from-classes/

Pas tout à fait le même sujet, mais pertinent pour cette règle.

Salut @gaearon , Voici l'exemple que vous m'avez demandé de poster ici (de tweeter) :)

Fondamentalement, j'essaie de convertir mon réact-trap de bibliothèque en crochets.
Ceci est juste un piège pour les événements, à l'extérieur / à l'intérieur d'un élément.

Mon problème est que si useEffect ne dépend trapped ), il est parfois périmé.
J'ai écrit quelques commentaires et journaux pour démontrer. Regardez le fichier useTrap.js , les commentaires et les logs sont dans les fonctions useEffect et preventDefaultHelper .

À ma connaissance, si une valeur n'est pas à l'intérieur de useEffect alors elle ne devrait pas faire partie de ses dépendances (corrigez-moi si je me trompe).

  1. Un CodeSandbox
  2. Pas:
    Un utilisateur clique à l'intérieur de la boîte pour la rendre active, et à l'extérieur pour la rendre inactive, l'utilisateur peut également cliquer avec le bouton droit de la souris, bien que pour le premier clic, cela ne devrait pas déclencher le menu contextuel ( e.preventDefault ).
    Quand je dis "premier clic", je veux dire le premier clic qui change l'état.
    Étant donné une boîte active, un clic droit en dehors de celle-ci changera l'état en "non actif" et empêchera le menu contextuel. un autre clic à l'extérieur n'affectera pas l'état, donc le menu contextuel devrait apparaître.

J'espère que je suis clair ici et que le cas d'utilisation est compréhensible, veuillez me faire savoir si je dois fournir plus d'informations. Merci!

Salut @gaearon ,

Il est élaboré à titre d'exemple, j'espère pouvoir l'expliquer de manière claire et compréhensible.

C'est l'état actuel de celui - ci :

Aperçu de la fonctionnalité
Aider l'utilisateur à gérer les appels asynchrones, les données résultantes et leur état tout au long du processus.

triggerAsyncData met asyncData manière asynchrone selon une fonction getData qui renvoie un Promise . triggerAsyncData peut être invoqué à la fois en tant qu'effet ou "manuellement" par l'utilisateur du hook.

Défis

  1. Les dépendances de l'effet qui invoque triggerAsyncData sont les plus intrinsèques. triggerAsyncData est une dépendance de l'effet, mais il est créé dans chaque rendu. Ligne de pensée jusqu'à présent:

    1. Ajoutez-le simplement en tant que dépendance => Mais ensuite, l'effet s'exécute à chaque rendu.

    2. Ajoutez-le en tant que dépendance, mais utilisez useMemo / useCallback avec triggerAsyncData => useMemo / useCallback ne doit être utilisé que pour des optimisations de performances AUTANT QUE JE SACHE.

    3. Scope it inside the effect => Alors je ne peux pas le retourner à l'utilisateur.

    4. Au lieu d'utiliser triggerAsyncData comme dépendance, utilisez les dépendances de triggerAsyncData comme dépendances => Meilleure option que j'ai trouvée jusqu'à présent. Mais cela enfreint la règle des « deps exhaustifs ».

  2. Chaque paramètre d'entrée du hook personnalisé est/devient une dépendance de notre effet interne. Ainsi, les fonctions en ligne et les objets littéraux en tant que paramètres font que l'effet s'exécute trop souvent.

    1. Laissez à l'utilisateur la responsabilité. Ils fourniront des valeurs appropriées, en utilisant useMemo / useCallback si nécessaire => J'ai bien souvent peur que ce ne soit pas le cas. Et s'ils le font, c'est assez verbeux.

    2. Autorisez un argument supplémentaire pour que le hook personnalisé fournisse les profondeurs des entrées, et utilisez-le à la place des entrées elles-mêmes => Cool, moins verbeux, plus de contrôle pour l'utilisateur. J'utilise ceci. Mais cela enfreint la règle des « deps exhaustifs ». (En fait, j'utilise I am this et je reviens aux deps réguliers si aucun argument supplémentaire n'est fourni. Je trouve que c'est un modèle puissant).

  3. Les dépendances mal gérées pour le hook personnalisé génèrent une boucle asynchrone infinie en raison de l'effet interne. J'ai utilisé une programmation défensive pour empêcher cela, mais cela ajoute un "faux" ? dépendance ( asyncData ). Cela enfreint à nouveau la règle des « deps exhaustifs ».

Explication plus longue que je ne le souhaitais... mais je pense que cela reflète mes difficultés à utiliser correctement les crochets. S'il vous plaît laissez-moi savoir s'il y a autre chose que je peux faire pour rendre ces difficultés plus claires.

Merci pour tout le travail acharné ici!

Hey @gaearon merci pour tout votre travail acharné.

  1. Exemple de récupération de données asynchrone minimale Exemple de CodeSandbox .

  2. L'utilisateur devrait voir 5 chaînes de titre lorem ipsum extraites de json api .

  3. J'ai créé un hook personnalisé pour la récupération de données avec l'API prévue :

const { data, isLoading, isError } = useDataApi('https://jsonplaceholder.typicode.com/posts')

Éléments internes du crochet useDataApi :

...
  const fetchData = async () => {
    let response;
    setIsError(false);
    setIsLoading(true);

    try {
      response = await fetch(url).then(response => response.json());

      setData(response);
    } catch (error) {
      setIsError(true);
    }

    setIsLoading(false);
  };

  useEffect(
    () => {
      fetchData();
    },
    [url]
  );
...

Le problème étant ce code

  useEffect(
    () => {
      fetchData();
    },
    [url]
  );

react-hooks/exhaustive-deps déclenche un avertissement indiquant que je dois ajouter fetchData à mon tableau de dépendances et que url doit être supprimé.

Si je change ce crochet en

  useEffect(
    () => {
      fetchData();
    },
    [fetchData]
  );

alors il lance des requêtes en continu et ne s'arrête jamais, ce qui est bien sûr un gros problème. Je ne sais pas si mon code est bogué ou si react-hooks/exhaustive-deps déclenche un faux positif.

Toute aide appréciée. Merci beaucoup.

PS J'ai lu votre commentaire sur useEffect pas adapté à la récupération de données , cependant, les documents de réaction affirment que Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects ce qui m'a donné confiance que les données useEffect sont idéales pour la récupération de données. Alors maintenant, je suis un peu confus 😕

@jan-stehlik, vous devez envelopper fetchData avec useCallback. j'ai forké votre codesandbox avec le changement nécessaire ici https://codesandbox.io/s/pjmjxprp0m

Très utile, merci beaucoup @viankakrisna !

À ma connaissance, si une valeur n'est pas dans useEffect, elle ne devrait pas faire partie de ses dépendances (corrigez-moi si je me trompe).

Je pense que vous vous trompez ici. Ou plutôt, un peu confus. Vous utilisez handleEvent à l' intérieur de votre effet. Mais vous ne le déclarez pas. C'est pourquoi les valeurs qu'il lit sont périmées.

Intéressant.

Vous utilisez handleEvent dans votre effet. Mais vous ne le déclarez pas. C'est pourquoi les valeurs qu'il lit sont périmées.

Qu'entendez-vous par : _"Mais vous ne le déclarez pas"_ ?
Il est déclaré sous l'effet (comme tous les autres gestionnaires).

Ou voulez-vous dire parce que le gestionnaire utilise la valeur d'état et que l'effet attache le gestionnaire, cela signifie que l'effet dépend de cette valeur d'état ?

Ou voulez-vous dire parce que le gestionnaire utilise la valeur d'état et que l'effet attache le gestionnaire, cela signifie que l'effet dépend de cette valeur d'état ?

Oui, si vous utilisez une fonction , vous devez soit déclarer en DEPS (et dans ce cas , l' envelopper avec useCallback pour éviter de le recréer), ou tout que les utilisations de la fonction.

Ok c'est une nouvelle pour moi ! Merci pour cette contribution @gaearon :)
Je veux juste que ce soit super clair pour moi (et les autres ?)...

Si un effet invoque, passe ou fait quoi que ce soit avec une fonction, nous devons passer à son tableau deps soit :
La fonction elle-même OU Les variables que cette fonction utilise.
Si la fonction est déclarée à l'intérieur du composant de fonction / du crochet personnalisé, il est conseillé de l'envelopper avec useCallback afin qu'elle ne soit pas recréée à chaque fois que notre composant ou crochet personnalisé est en cours d'exécution.

Je dois dire que je ne l'ai pas vu sur la doc .
Pensez-vous qu'il est possible de l'ajouter à la section _Note_ ?

Noter
Le tableau d'entrées n'est pas passé en tant qu'arguments à la fonction d'effet. Conceptuellement, cependant, c'est ce qu'ils représentent : chaque valeur référencée dans la fonction d'effet doit également apparaître dans le tableau des entrées. À l'avenir, un compilateur suffisamment avancé pourrait créer ce tableau automatiquement.

Éditer
Encore une chose, dans mon exemple, quels sont les deps pour useCallback quand il enveloppe handleEvent (ou tout autre gestionnaire pour cette raison). est-ce le event lui-même ?

Je dois dire que je ne l'ai pas vu sur la doc.

La documentation dit que "chaque valeur référencée dans la fonction d'effet doit également apparaître dans le tableau des entrées". Les fonctions sont aussi des valeurs. Je suis d'accord que nous devons mieux documenter cela - c'est le sujet de ce fil :-) Je collecte des cas d'utilisation pour une nouvelle page de documentation dédiée à cela.

Encore une chose, dans mon exemple, quels sont les deps pour useCallback lorsqu'il enveloppe handleEvent (ou tout autre gestionnaire pour cette raison). est-ce l'événement lui-même ?

Pas sûr de ce que vous voulez dire. Il s'agit de toutes les valeurs référencées par la fonction en dehors de celle-ci. Tout comme dans useEffect .

Je suppose que je n'y ai pas pensé avec les fonctions en tant que dépendances. mon modèle mental était faux, je pensais passer une fonction en tant que dépendance uniquement si elle est entrée en tant qu'accessoire ou argument de mon composant / crochet. Merci de m'avoir clarifié cela.

Quant au useCallback , je l'ai utilisé comme ceci :

const memoHandleEvent = useCallback(
    handleEvent
);

et bien sûr passé memoHandleEvent tant que dépendance pour useEffect ainsi que addEventListener place de la vraie fonction handleEvent . semble fonctionner, j'espère que c'est la manière appropriée et idiomatique de le faire.

Remarque useCallback sans deuxième argument ne fait rien.

Encore une fois, un bac à sable complet serait nécessaire. Je ne peux pas dire si votre correctif est correct par une description incomplète comme celle-ci.

Notez que useCallback sans second argument ne fait rien.

Argg ! :grimant: lol

Encore une fois, un bac à sable complet serait nécessaire. Je ne peux pas dire si votre correctif est correct par une description incomplète comme celle-ci.

Aw, c'est le même lien d'en haut. je viens de le mettre à jour :)

Veuillez ne pas mettre à jour les liens CodeSandbox : PI en a besoin tels qu'ils étaient à l'origine - sinon je ne peux pas tester la règle de lint sur eux. Pourriez-vous s'il vous plaît créer deux bacs à sable distincts si vous avez deux solutions différentes ? Je peux donc vérifier chacun.

Oops désolé! :PI a dépassé le nombre de bacs à sable sur mon compte. permettez-moi d'en supprimer et j'en créerai un autre (et annulerai les modifications de l'original).

@gaearon c'est le deuxième lien vers la solution avec useCallback

J'ai un scénario qui, je crois, est correct, mais le linter se plaint. Mon échantillon :

CodeSandbox

Ce que l'échantillon essaie de représenter, c'est lorsqu'un utilisateur clique sur un bouton, puis une demande est faite pour demander de nouvelles données sur la base d'un identifiant et d'une fonction fournis précédemment. Si l'ID change, il ne devrait pas demander de nouvelles données ; seule une nouvelle demande de rechargement doit déclencher une nouvelle demande de données.

L'exemple ici est un peu artificiel. Dans ma vraie application, React vit dans un DIV qui est une petite partie d'une application Web beaucoup plus grande. La fonction qui est transmise est via Redux et mapDispatchToProps, où une création d'action prend l'ID et fait une demande ajax pour récupérer les données et mettre à jour le magasin. La prop refreshRequest est transmise via React.createElement. Dans mon implémentation d'origine, j'avais un code qui ressemblait à ceci dans le composant de classe :

componentDidUpdate (prevProps) { const { getData, someId, refreshRequest} = this.props; if (prevProps.refreshRequest!== this.props.refreshRequest) { getData(someId); } }

J'essaie d'implémenter le même comportement avec un crochet d'effet. Mais comme écrit dans l'échantillon, le linter se plaint :

avertissement React Hook useEffect a des dépendances manquantes : 'getData' et 'someId'. Soit les inclure, soit supprimer le tableau de dépendances

Si j'ajoute tout ce que veut le linter, alors si l'utilisateur clique sur l'un des boutons de l'exemple, useEffect est déclenché. Mais je veux seulement qu'il soit déclenché lorsque le bouton Demander de nouvelles données est enfoncé.

Espérons que cela ait du sens. Je serais heureux de clarifier davantage, si quelque chose n'est pas clair. Merci!

Je viens de publier [email protected] qui a un support expérimental pour détecter les dépendances de fonctions nues (qui ont tendance à ne pas être très utiles sans useCallback ). Voici un gif :

demo

J'adorerais si vous pouviez tous l'essayer dans vos projets et voir ce que vous ressentez ! (Veuillez commenter ce flux spécifique dans https://github.com/facebook/react/pull/15026.)

Je vais l'essayer sur des exemples de ce fil demain.

Je ne l'ai pas encore essayé mais je me demande s'il s'agit de levage. Ceci est juste un "exemple minimum" que j'ai trouvé sur place, donc ce n'est utile pour rien, mais j'utilise beaucoup les déclarations hissées pour faciliter la visualisation de l'instruction return .

function Component() {
  useEffect(() => {
    handleChange
  }, [handleChange])

  return null

  function handleChange() {}
}

Ce serait bien si la règle avait une option pour configurer une autre fonction pour qu'elle se comporte comme useEffect en ce qui concerne le linter. Par exemple, j'ai ajouté ceci afin que je puisse facilement utiliser des fonctions asynchrones pour les effets qui effectuent un appel AJAX - cependant, comme cela, je perds tous les avantages de exhaustive-deps linting :

const useAsyncEffect = (fn, ...args) => {
    /* eslint-disable react-hooks/exhaustive-deps */
    useEffect(() => {
        fn();
    }, ...args);
};

Edit : Tant pis, je viens de remarquer que c'est déjà possible en utilisant l'option additionalHooks de la règle.

Salut tout le monde,
J'ai un exemple https://codesandbox.io/s/znnmwxol7l

Ci-dessous ce que je veux :

function App() {
  const [currentTime, setCurrentTime] = React.useState(moment());

  const currentMonth = React.useMemo(
    () => {
      console.log("RUN");
      return currentTime.format("MMMM");
    },
    [currentTime.format("MMMM")] // <= this proplem [currentTime]
  );

  return (
    <div className="App">
      <h1>Current month: {currentMonth}</h1>
      <div>
        <button
          onClick={() => setCurrentTime(currentTime.clone().add(1, "days"))}
        >
          + 1 day
        </button>
      </div>
      <div>{currentTime.toString()}</div>
    </div>
  );
}

Mais voici ce que j'obtiens :

  const currentMonth = React.useMemo(
    () => {
      console.log("RUN");
      return currentTime.format("MMMM");
    },
    [currentTime] // <= this proplem
  );

Chaque fois que le currentTime change alors currentMonth recalcule inutile

Ou d'une manière ou d'une autre, je peux le faire ci-dessous:

  const currentMonth = React.useMemo(
    () => {
      return currentTime.format("MMMM");
    },
    [myEqualityCheck(currentTime)]
  );

Désolé si cela a déjà été répondu, je n'ai pas pu le voir dans le fil ci-dessus:
La façon d'exécuter le hook useEffect uniquement sur le montage consiste à définir un tableau d'entrées vide comme deuxième paramètre. Cependant, en faisant cela, les plaintes exhaustives concernant l'inclusion de ces arguments d'entrée, ce qui modifierait l'effet pour qu'il s'exécute également lors de la mise à jour.
Quelle est l'approche pour exécuter useEffect uniquement sur le montage avec des deps exhaustifs activés ?

@einarq Je suppose que vous devez vous assurer que toutes les valeurs référencées dans votre useEffect sur la monture ne changeront jamais. Cela pourrait être réalisé en utilisant d'autres crochets comme useMemo . Après cela, que cette règle ESlint ajoute ou non toutes les références dans un tableau (avec correction automatique), le code ne sera exécuté qu'une seule fois.

@einarq Comme indiqué ailleurs dans le fil, nous ne pouvons pas vous aider sans CodeSandbox. Parce que la réponse dépend vraiment de votre code.

@nghiepit Votre exemple n'a pas vraiment de sens pour moi. Si votre entrée est currentTime.format("MMMM") alors useMemo n'optimise rien pour vous car vous l'avez déjà calculé . Vous le calculez donc deux fois inutilement.

Est-il possible de spécifier quel index d'argument est le rappel sur l'option additionalHooks ? Je vois que nous supposons en ce moment dans le code que ce serait le premier https://github.com/facebook/react/blob/9b7e1d1389e080d19e71680bbbe979ec58fa7389/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js#L1051 - L1081

Pas possible actuellement. Notre conseil général est que les Hooks personnalisés devraient préférer ne pas avoir d'argument deps car il devient très difficile de penser à la façon dont les deps composent. Si vous prenez une fonction, vous voudrez peut-être demander aux utilisateurs de useCallback place.

@CarlosGines Pouvez-vous s'il vous plaît créer une CodeSandbox, comme demandé dans le message OP. Je pose cette question pour une raison, sinon il m'est difficile de vérifier les modifications. Surtout quand il est écrit en TypeScript.

Je viens de publier [email protected] avec, espérons-le, des messages plus utiles et des heuristiques plus intelligentes. Veuillez l'essayer sur vos projets avant qu'il ne devienne stable.

Je m'attends à ce qu'avec la plupart des exemples de ce fil, il donne des conseils raisonnables qui devraient vous aider à résoudre les problèmes.

cela devrait-il être [email protected] ?

Oui.

En fait, je viens de publier [email protected] avec quelques petits changements.

@gaearon Avez-vous réussi à faire fonctionner eslint dans la codesandbox par hasard ?

@einarq peut-être que quelque chose comme ça fonctionnerait?

const useDidMount = fn => {
  const callbackFn = useCallback(fn)
  useEffect(() => {
    callbackFn()
  }, [callbackFn])
}

Je suis en fait gêné de dire que je ne parviens pas à faire fonctionner le plugin sur une simple application CRA (basée sur mon extrait de code ).
voici le repo fork de codesandbox.

J'ai vu cette note dans la doc :

Remarque : si vous utilisez Create React App, veuillez attendre une version correspondante de react-scripts qui inclut cette règle au lieu de l'ajouter directement.

Mais n'y a-t-il aucun moyen de le tester avec CRA pour le moment ? ??

Oui, vous devrez l'éjecter et l'ajouter à la configuration ESLint jusqu'à ce que nous l'y ajoutions. Vous pouvez l'éjecter dans une branche et ne pas la fusionner. :-)

Salut,

tout d'abord : merci beaucoup pour votre collaboration si étroite avec la communauté et le travail incroyable que vous faites en général !

Nous avons un problème avec un hook personnalisé que nous avons construit autour de l'API Fetch . J'ai créé une Codesandbox pour illustrer le problème.

Exemple sur Codesandbox

https://codesandbox.io/s/kn0km7mzv

Remarque : le problème (voir ci-dessous) entraîne une boucle infinie et je ne veux pas DDoS jsonplaceholder.typicode.com que j'utilise pour démontrer le problème. Par conséquent, j'ai inclus un simple limiteur de requêtes utilisant un compteur. Ce n'est pas nécessaire pour démontrer le problème, mais cela nous a semblé mal de lancer un nombre illimité de demandes pour ce grand projet.

Explication

L'idée est de faciliter la gestion des 3 états possibles d'une requête API : Loading, Success et Error. Ainsi nous avons construit un hook personnalisé useFetch() qui renvoie 3 propriétés isLoading , response et error. It makes sure that either réponse or error is set and updates isLoading . As the name implies, it uses the Fetch` API.

Pour ce faire, il utilise 3 useState hooks :

  const [isLoading, setIsLoading] = useState(false);
  const [response, setResponse] = useState(null);
  const [error, setError] = useState(null);

et un crochet useEffect :

useEffect(
    () => {
      fetch(url, fetchConfig)
        .then(/* Handle fetch response... See Codesandbox for the actual implementation */)
    },
    [url]
  );

Cela fonctionne bien tant que le tableau de dépendances de useEffect ne contient que [url] . Si nous ajoutons fetchConfig (= [url, fetchConfig] ), il en résulte une boucle infinie. Pour notre cas d'utilisation particulier, il suffirait de ne réexécuter l'effet que lorsque le url change mais le linter ne permet pas d'utiliser uniquement [url] (testé avec v1.4.0 et v1.5.0-beta.1 ).

À la fin du hook personnalisé, les 3 variables d'état sont renvoyées sous forme d'objet :

  return {
    error,
    isLoading,
    response
  };

Je ne sais pas si c'est le bon endroit pour demander des conseils sur cette question, car la suggestion de linting a du sens, je suppose. Désolé si ce n'est pas le cas.

Et si fetchConfig change ? Pourquoi vous attendez-vous à ce qu'il soit statique?

D'accord. J'ai publié [email protected] avec les correctifs et améliorations de la semaine dernière.
Ce fil a été extrêmement utile pour trouver différents modèles qui causent des problèmes.

Merci beaucoup à vous tous. (Surtout ceux qui ont fourni des bacs à sable. :-)


Je ne répondrai pas personnellement à tout le monde en détail car c'est beaucoup de cas.

Au lieu de cela, nous rédigerons des recettes et des pièges courants dans les prochains jours, et nous créerons un lien vers eux à partir de la documentation. Je vais m'assurer que chaque modèle commun dans ce fil est couvert (ou demander à suivre dans un numéro séparé pour les modèles rares).

Une fois les exemples publiés, je commenterai ici et modifierai chaque commentaire contenant un CodeSandbox en ajoutant un lien à la réponse / l'exemple pertinent qui illustre le bon correctif. J'espère que cela vous aidera ainsi que les futurs lecteurs.

À votre santé!

❤️

@timkraut, vous devriez pouvoir ajouter fetchConfig sur les deps, et le composant devrait l'envelopper avec un mémo pour que la référence reste.
Un exemple : https://codesandbox.io/s/9l015v2x4w


Mon problème avec ceci est que maintenant le composant doit être au courant des détails de mise en œuvre du crochet ...

Je ne sais pas si ce fil est toujours ouvert à des exemples de discussion, mais je suis toujours en train de me concentrer sur les meilleures pratiques. Ma question concerne le contrôle du moment où le crochet useEffect se déclenche avec le tableau de dépendances et l'utilisation des valeurs à ce moment-là plutôt que la déclaration d'une référence distincte pour contourner la règle de lint.

Exemple

https://codesandbox.io/s/40v54jnkyw

Dans cet exemple, j'essaie d'enregistrer périodiquement les valeurs d'entrée.

Explication

Toutes les 5 secondes, le code essaie d'enregistrer automatiquement les valeurs actuelles (il suffit de les imprimer à l'écran pour le moment). Le linter nécessiterait que toutes les valeurs d'entrée soient incluses dans le tableau de dépendances, ce qui modifierait la fréquence à laquelle l'effet se déclencherait.

  useEffect(
    () => {
      if (autoSaveTick % 5 === 0) {
        setAutosaveValue(
          `${input1Value.value}, ${input2Value.value}, ${input3Value.value}`
        );
      }
    },
    [autoSaveTick]
  );

L'alternative que je vois est d'avoir une implémentation similaire aux crochets useTick et useInterval définis dans l'exemple, où un rappel est affecté à une référence. Il semble que cela entraînerait des redéfinitions inutiles des fonctions juste pour s'aligner sur la règle de la charpie.

Je recherche les meilleures pratiques pour écrire un effet qui s'exécute lorsqu'une variable change (Variable A) qui utilise d'autres valeurs de variable (Variable B et Variable C) au moment du changement de variable précédent (Variable A).

Et si fetchConfig change ? Pourquoi vous attendez-vous à ce qu'il soit statique?

Je serais bien de l'ajouter au tableau des dépendances. Dans notre cas particulier, cela ne peut jamais arriver, mais je suppose que c'est une bonne idée de l'ajouter quand même.

Mon problème avec ceci est que maintenant le composant doit être au courant des détails de mise en œuvre du crochet ...

C'est exactement le problème que nous avons aussi :/ Dans notre implémentation, nous avons même utilisé une propriété supplémentaire pour faciliter la définition du corps. Nous devrions donc utiliser 2 appels useMemo() chaque fois que nous utilisons ce hook. Vous ne savez pas encore comment surmonter cette limitation. Si vous avez une idée, faites le moi savoir !

Comment pouvez-vous exécuter un useEffect qui n'a de dépendances qu'une seule fois sans que la règle se plaigne lors du passage d'un tableau vide ?

Dites, quelque chose comme :

useEffect(() => { init({ dsn, environment}) }, [])

Mon problème avec ceci est que maintenant le composant doit être au courant des détails de mise en œuvre du crochet ...

Je ne dirais pas que ce sont des détails de mise en œuvre. C'est votre API. Si un objet est passé, nous supposons qu'il peut changer à tout moment.

Si en pratique il est toujours statique, vous pouvez en faire un argument pour une fabrique de Hook. Comme createFetch(config) qui renvoie useFetch() . Vous appelez l'usine au plus haut niveau.

Je comprends que c'est un peu bizarre. Nous avons un problème similaire avec le useSubscription lequel nous travaillons. Mais comme il s'agit d'un problème courant, cela peut signifier que useMemo est en fait une réponse légitime et que les gens devraient simplement s'habituer à le faire pour ces cas.

En pratique, nous nous pencherons davantage sur cela à l'avenir. Mais vous ne devez pas traiter un objet potentiellement dynamique comme un objet statique, car un utilisateur ne peut pas le modifier.

@asylejmani Vous n'avez fourni aucun détail sur vos cas d'utilisation, comme demandé dans le post du haut. Pourquoi vous attendez-vous à ce que quelqu'un puisse répondre à votre question ?

Le but de la règle est de vous dire que environment et dsn peuvent changer avec le temps et que votre composant doit gérer cela. Donc, soit votre composant est bogué (car il ne gère pas les modifications apportées à ces valeurs), soit vous avez un cas unique où cela n'a pas d'importance (auquel cas vous devez ajouter une règle de lint ignorer le commentaire expliquant pourquoi).

Dans les deux cas, ce que vous demandez n'est pas clair. La règle se plaint que les choses peuvent changer, et vous ne gérez pas ce changement. Sans un exemple complet, vous seul pouvez expliquer pourquoi vous pensez que le traitement n'est pas nécessaire.

@gaearon désolé de ne pas être plus clair (ça sonne toujours clair dans la tête) :) ma question est plutôt de savoir comment réaliseriez-vous componentDidMount avec useEffect.

J'avais l'impression qu'un tableau vide le fait, mais la règle me dit de toujours inclure des dépendances dans le tableau.

@asylejmani Je pense que le plus gros problème avec les méthodes de cycle de vie des classes comme componentDidMount est que nous avons tendance à la considérer comme une méthode isolée, mais en fait, cela fait partie d'un flux.
Si vous référencez quelque chose dans componentDidMount vous aurez probablement besoin de le gérer également dans componentDidUpdate , ou votre composant peut devenir bogué.
C'est ce que la règle essaie de corriger, vous devez gérer les valeurs au fil du temps.

  1. composant monté, faire quelque chose avec un accessoire
  2. composant mis à jour (par exemple : la valeur de la prop a changé), faites quelque chose avec la nouvelle valeur de la prop

Le numéro 1 est l'endroit où vous mettez la logique dans componentDidMount / useEffect body
Le numéro 2 est l'endroit où vous mettez la logique dans componentDidUpdate / useEffect deps

La règle est de se plaindre que vous ne faites pas la partie numéro 2 du flux

@gaearon désolé de ne pas être plus clair (ça sonne toujours clair dans la tête) :) ma question est plutôt de savoir comment réaliseriez-vous componentDidMount avec useEffect.

J'avais l'impression qu'un tableau vide le fait, mais la règle me dit de toujours inclure des dépendances dans le tableau.

Je pense que moi et @asylejmani sommes sur la même @gaearon, c'est que nous avons probablement tort de
Est-ce une déclaration juste? Je suppose que je pense que fournir un tableau vide est un peu comme dire "Je sais ce que je fais", mais je comprends pourquoi vous voulez toujours que la règle soit en place.

Désolé de ne pas avoir encore fourni de bac à sable. J'ai commencé l'autre soir avec un exemple de création d'application React, je n'ai pas trouvé comment exécuter eslint sur le bac à sable, puis j'ai perdu le bac à sable lors du rechargement du navigateur sans enregistrer d'abord (en supposant que CodeSandbox temp stocké, mon mauvais).
Ensuite, j'ai dû aller me coucher et je n'ai pas eu le temps depuis.

Dans tous les cas, je comprends ce que vous dites, et que dans des scénarios normaux, il est probablement préférable d'inclure ces dépendances et de ne pas supposer qu'il suffit de s'exécuter uniquement sur le montage.

Il existe probablement des cas d'utilisation valables pour cela aussi, mais c'est un peu difficile à expliquer ou à trouver un bon exemple, donc je vais vivre en désactivant la règle en ligne si nécessaire.

@asylejmani est votre cas d'utilisation similaire à https://github.com/facebook/react/issues/14920#issuecomment -466378650? Je ne pense pas que la règle puisse comprendre le scénario dans ce cas, nous devrons donc simplement la désactiver manuellement pour ce type de code. Dans tous les autres cas, la règle fonctionne comme il se doit.

Je ne sais pas si cela a du sens, mais un scénario qui est assez courant pour moi est quelque chose comme ceci :

useEffect(() => {
    if(!dataIsLoaded) { // flag from redux
        loadMyData(); // redux action creator
    }
}, []);

Ces deux dépendances proviennent de redux. Les données (dans ce cas) ne doivent être chargées qu'une seule fois et le créateur de l'action est toujours le même.

Ceci est spécifique au redux, et votre règle eslint ne peut pas le savoir, donc je comprends pourquoi elle devrait avertir. Vous vous demandez toujours si fournir un tableau vide devrait peut-être simplement désactiver la règle ? J'aime que la règle me dise sur les deps manquants si j'en ai fourni certains mais pas tous, ou si je n'en ai fourni aucun. Un tableau vide signifie quelque chose de différent pour moi. Mais c'est peut-être moi :)

Merci pour tout votre travail acharné! Et pour rendre notre vie de développeurs meilleure :)

Mon cas d'utilisation est beaucoup plus simple et je peux bien sûr ajouter toutes les dépendances et cela fonctionnera toujours de la même manière, cependant j'avais l'impression que la règle "avertit" lorsque vous avez des dépendances mais qu'il en manque d'autres.

Le cas d'utilisation de @einarq est quelque chose que j'ai utilisé plusieurs fois, par exemple sur "componentDidMount" charge les données s'il n'y en a pas (de redux ou autre).

Je suis également d'accord que dans ces cas, la désactivation de la règle en ligne est le meilleur choix. Ce cas, vous savez exactement ce que vous faites.

Je crois que toute ma confusion était [] contre [certains], et bien sûr merci @gaearon pour le travail formidable :)

Je pense que moi et @asylejmani sommes sur la même @gaearon, c'est que nous avons probablement tort de

Oui. Si votre composant ne gère pas les mises à jour d'un accessoire, il est généralement bogué. Le design de useEffect vous oblige à l'affronter. Vous pouvez bien sûr contourner ce problème, mais la valeur par défaut est de vous pousser à gérer ces cas. Ce commentaire l'explique bien : https://github.com/facebook/react/issues/14920#issuecomment -470913287.

Ces deux dépendances proviennent de redux. Les données (dans ce cas) ne doivent être chargées qu'une seule fois et le créateur de l'action est toujours le même.

Si c'est la même chose, l'inclure dans les dépendances ne vous fera pas de mal. Je tiens à le souligner - si vous êtes sûr que vos dépendances ne changent jamais, il n'y a aucun mal à les répertorier . Cependant, s'il arrive plus tard qu'ils changent (par exemple, si un composant parent passe une fonction différente selon l'état), votre composant gérera cela correctement.

Vous vous demandez toujours si fournir un tableau vide devrait peut-être simplement désactiver la règle ?

Non. Fournir un tableau vide, puis se demander pourquoi certains accessoires ou certains états sont périmés est littéralement l'erreur la plus courante.

>

C'est plein de sens, merci

Le 8 mars 2019, à 15h27, Dan Abramov [email protected] a écrit :

Je pense que moi et @asylejmani sommes sur la même @gaearon, c'est que nous avons probablement tort de

Oui. Si votre composant ne gère pas les mises à jour d'un accessoire, il est généralement bogué. La conception de useEffect vous oblige à vous y confronter. Vous pouvez bien sûr contourner ce problème, mais la valeur par défaut est de vous pousser à gérer ces cas. Ce commentaire l'explique bien : #14920 (commentaire).

Ces deux dépendances proviennent de redux. Les données (dans ce cas) ne doivent être chargées qu'une seule fois et le créateur de l'action est toujours le même.

Si c'est la même chose, l'inclure dans les dépendances ne vous fera pas de mal. Je tiens à le souligner - si vous êtes sûr que vos dépendances ne changent jamais, il n'y a aucun mal à les répertorier. Cependant, s'il arrive plus tard qu'ils changent (par exemple, si un composant parent passe une fonction différente selon l'état), votre composant gérera cela correctement.

Vous vous demandez toujours si fournir un tableau vide devrait peut-être simplement désactiver la règle ?

Non. Fournir un tableau vide, puis se demander pourquoi certains accessoires ou certains états sont périmés est littéralement l'erreur la plus courante.

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub ou coupez le fil de discussion.

@aweary

Cela entraînerait l'effet secondaire avant que la mise à jour réelle ne soit validée, ce qui pourrait provoquer le déclenchement de l'effet secondaire plus souvent que vous ne le souhaitez.

Je ne suis pas sûr de ce que cela signifie. Pouvez vous donner un exemple?

Nous avons fait une passe dessus avec @threepointone aujourd'hui. Voici un résumé :

Corrigé dans la règle Lint

Dépendances useEffect étrangères

La règle ne vous empêche plus d'ajouter des deps "étrangers" à useEffect car il existe des scénarios légitimes.

Fonctions dans le même composant mais définies en dehors de l'effet

Linter n'avertit pas des cas où il est sûr maintenant, mais dans tous les autres cas, il vous donne de meilleures suggestions (comme déplacer la fonction à l'intérieur de l'effet ou l'envelopper avec useCallback ).

Vaut la peine d'être corrigé dans le code utilisateur

Réinitialisation de l'état sur le changement d'accessoires

Cela ne produit plus de violations de peluches, mais la manière idiomatique de réinitialiser l'état en réponse aux accessoires est différente . Cette solution aura un rendu supplémentaire incohérent, donc je ne suis pas sûr que ce soit souhaitable.

"Ma valeur de non-fonction est constante"

Les crochets vous poussent vers l'exactitude dans la mesure du possible. Si vous spécifiez les DEPS (qui , dans certains cas , vous pouvez omettre), nous vous recommandons fortement d'inclure même ceux que vous pensez ne changera pas. Oui, dans cet exemple de useDebounce est peu probable que le délai change. Mais c'est toujours un bug si c'est le cas, mais le Hook ne peut pas le gérer. Cela apparaît également dans d'autres scénarios. (Par exemple, les crochets sont beaucoup plus compatibles avec le rechargement à chaud car chaque valeur est traitée comme dynamique.)

Si vous insistez absolument sur le fait qu'une certaine valeur est statique, vous pouvez l'appliquer.
Le moyen le plus sûr est de le faire explicitement dans votre API :

const useFetch = createFetch({ /* config object */});
const useDebounce = createDebounce(500);
const FormInput = createInput({ rules: [emailValidator, phoneValidator] });

Ensuite, il ne peut <Slider min={50} /> ne peut jamais changer n'est pas vraiment valable — quelqu'un pourrait facilement le changer en <Slider min={state ? 50 : 100} /> . En fait, quelqu'un pourrait faire ça :

let slider
if (isCelsius) {
  slider = <Slider min={0} max={100} />
} else {
  slider = <Slider min={32} max={212} />
}

Si quelqu'un change d'état isCelsius , un composant supposant que min ne change jamais ne pourra pas se mettre à jour. Il n'est pas évident dans ce cas que le Slider sera le même (mais ce sera parce qu'il a la même position dans l'arbre). Il s'agit donc d'une arme de poing majeure en termes de modifications du code. Un point majeur de React est que les mises à jour sont rendues exactement comme les états initiaux (vous ne pouvez généralement pas dire lequel est lequel). Que vous rendiez la valeur de prop B ou que vous passiez de la valeur de prop A à B, cela devrait ressembler et se comporter de la même manière.

Bien que cela soit déconseillé, dans certains cas, le mécanisme d'application peut être un Hook qui avertit lorsque la valeur change (mais fournit la première). Au moins alors il est plus susceptible d'être remarqué.

function useMyHook(a) {
  const initialA = usePossiblyStaleValue(a);
  // ...
}

function usePossiblyStaleValue(value) {
  const ref = useRef(value);

  if (process.env.NODE_ENV !== 'production') {
    if (ref.current !== value) { // Or your custom comparison logic
      console.error(
        'Unlike normally in React, it is not supported ' +
        'to pass dynamic values to useMyHook(). Sorry!'
      );
    }
  }

  return ref.current;
}

Il peut également y avoir un cas légitime où vous ne pouvez tout simplement key sur les mises à jour incompatibles, forçant un remontage propre avec de nouveaux accessoires. C'est probablement préférable pour les composants feuilles comme les curseurs ou les cases à cocher.

"Ma valeur de fonction est constante"

Tout d'abord, s'il est constant et hissé au plus haut niveau, le linter ne se plaindra pas. Mais cela n'aide pas avec les choses provenant des accessoires ou du contexte.

S'il est vraiment constant, le spécifier dans deps ne fait pas de mal. Comme dans le cas où une fonction setState l'intérieur d'un Hook personnalisé est renvoyée à votre composant, puis vous l'appelez à partir d'un effet. La règle des peluches n'est pas assez intelligente pour comprendre une telle indirection. Mais d'un autre côté, n'importe qui peut encapsuler ce rappel plus tard avant de revenir, et éventuellement référencer un autre accessoire ou état à l'intérieur. Alors ce ne sera pas constant ! Et si vous ne parvenez pas à gérer ces changements, vous aurez de vilains bogues de prop/state périmés. Donc, le spécifier est une meilleure valeur par défaut.

Cependant, c'est une idée fausse que les valeurs des fonctions sont nécessairement constantes. Ils sont plus souvent constants dans les classes en raison de la liaison de méthode, bien que cela crée sa propre gamme de bogues . Mais en général, toute fonction qui se ferme sur une valeur dans un composant de fonction ne peut pas être considérée comme constante. La règle des peluches est maintenant plus intelligente pour vous dire quoi faire. (Comme le déplacer à l' useCallback .)

Il y a un problème sur le spectre opposé de cela, c'est là que vous obtenez des boucles infinies (une valeur de fonction change toujours). Nous attrapons cela dans la règle des peluches maintenant lorsque cela est possible (dans le même composant) et suggérons un correctif. Mais c'est délicat si vous passez quelque chose de plusieurs niveaux vers le bas.

Vous pouvez toujours l'envelopper dans useCallback pour résoudre le problème. N'oubliez pas que techniquement, il est valide qu'une fonction change , et vous ne pouvez pas ignorer ce cas sans risquer des bogues. Comme onChange={shouldHandle ? handleChange : null} ou le rendu de foo ? <Page fetch={fetchComments /> : <Page fetch={fetchArticles /> au même endroit. Ou même fetchComments qui se ferme sur l'état du composant parent. Cela peut changer. Avec les classes, son comportement changera silencieusement mais la référence de fonction restera la même. Ainsi, votre enfant manquera cette mise à jour - vous n'avez pas vraiment d'autre choix que de transmettre plus de données à l'enfant. Avec les composants de fonction et useCallback , l'identité de la fonction elle-même change, mais uniquement lorsque cela est nécessaire. C'est donc une propriété utile et pas seulement un obstacle à éviter.

Nous devrions ajouter une meilleure solution pour détecter les boucles asynchrones infinies. Cela devrait atténuer l'aspect le plus déroutant. Nous pourrions ajouter une détection pour cela à l'avenir. Vous pouvez également écrire quelque chose comme ceci vous-même :

useWarnAboutTooFrequentChanges([deps]);

Ce n'est pas idéal et nous devrons réfléchir davantage à la manière de gérer cela avec élégance. Je suis d'accord que les cas comme celui-ci sont assez désagréables. Le correctif sans enfreindre la règle serait de rendre rules statique, par exemple en changeant l'API en createTextInput(rules) , et d'envelopper register et unregister dans useCallback . Mieux encore, supprimez register et unregister , et remplacez-les par un contexte séparé où vous mettez dispatch seul. Ensuite, vous pouvez garantir que vous n'aurez jamais une identité de fonction différente de la lecture.

J'ajouterais que vous voudrez probablement mettre useMemo sur la valeur de contexte de toute façon parce que le fournisseur fait beaucoup de calculs qu'il serait triste de répéter si aucun nouveau composant n'était enregistré mais que son propre parent était mis à jour. Donc, ce genre de problème rend le problème que vous n'auriez peut-être pas remarqué autrement plus visible. Bien que je convienne que nous devons le rendre encore plus important lorsque cela se produit.

Ignorer complètement les dépendances de fonction conduit à de pires bogues avec les composants de fonction et les Hooks, car ils continueraient à voir des accessoires et des états périmés si vous le faites. Alors essayez de ne pas le faire, quand vous le pouvez.

Réagir aux changements de valeur composée

C'est étrange pour moi pourquoi cet exemple utilise un effet pour quelque chose qui est essentiellement un gestionnaire d'événements. Faire le même "journal" (je suppose que cela pourrait être une soumission de formulaire) dans un gestionnaire d'événements semble plus approprié. Cela est particulièrement vrai si nous pensons à ce qui se passe lorsque le composant se démonte. Que se passe-t-il s'il se démonte juste après la programmation de l'effet ? Des choses comme la soumission de formulaire ne devraient pas simplement « ne pas arriver » dans ce cas. Il semble donc que l'effet pourrait être un mauvais choix là-bas.

Cela dit, vous pouvez toujours faire ce que vous avez essayé - en faisant en sorte que fullName soit setSubmittedData({firstName, lastName}) place, puis [submittedData] est votre dépendance, à partir de laquelle vous pouvez lire firstName et lastName .

Intégration avec le code impératif/ancien

Lors de l'intégration avec des éléments impératifs tels que des plugins jQuery ou des API DOM brutes, une certaine méchanceté peut être attendue. Cela dit, je m'attendrais toujours à ce que vous puissiez consolider un peu plus les effets dans cet exemple.


J'espère que je n'ai oublié personne ! Faites-moi savoir si je l'ai fait ou si quelque chose n'est pas clair. Nous allons essayer de transformer les leçons de cela en quelques documents bientôt.

@gaearon , merci d'avoir pris le temps de plonger dans les problèmes et de résumer les actions en différentes catégories. Vous avez manqué mon échantillon (#14920 (commentaire) par @trevorgithub) dans votre commentaire de synthèse de clôture. (J'apprécie certainement qu'il y ait eu beaucoup de commentaires de la part de nombreuses personnes ; je pense que mon commentaire d'origine s'est perdu dans la section des éléments cachés quelque part au milieu des commentaires du problème).

Je suppose que mon échantillon relèverait de la catégorie « Intégrer avec le code impératif/ancien », bien que peut-être d'autres catégories ?

Dans le cas des problèmes d'intégration avec le code impératif/ancien, il semble qu'il n'y ait peut-être pas grand-chose à faire. Dans ces cas, comment ignorer cet avertissement ? J'imagine:
// eslint-disable-line react-hooks/exhaustive-deps

Désolé j'ai raté celui-ci.

Si vous recevez des données via des accessoires mais que vous ne souhaitez pas utiliser ces accessoires jusqu'à ce qu'un changement explicite soit apporté, cela semble être un moyen correct de le modéliser serait d'avoir un état dérivé.

Vous y pensez comme "Je veux ignorer un changement d'accessoire jusqu'à un autre accessoire". Mais vous pouvez aussi y penser comme « Mon composant a une fonction de récupération dans l'état. Il est mis à jour à partir d'un accessoire lorsqu'un autre accessoire change.

L'état généralement dérivé n'est pas recommandé, mais ici, cela semble être ce que vous voulez. La façon la plus simple de mettre en œuvre cela serait quelque chose comme :

const [currentGetData, setCurrentGetData] = useState(getData);
const [prevRefreshRequest, setPrevRefreshRequest] = useState(refreshRequest);

if (prevRefreshRequest !== refreshRequest) {
  setPrevRefreshRequest(refreshRequest);
  setCurrentGetData(getData);
}

useEffect(() => {
  currentGetData(someId);
}, [currentGetData, someId]);

Vous devrez également mettre useCallback autour des getData que vous transmettez.

Notez qu'en général, le modèle de transmission des fonctions asynchrones pour la récupération me semble louche. Je suppose que dans votre cas, vous utilisez Redux, donc cela a du sens. Mais si la fonction async était définie dans un composant parent, cela serait suspect car vous auriez probablement des conditions de concurrence. Vos effets n'ont pas de nettoyage, alors comment pouvez-vous savoir quand l'utilisateur choisit un autre identifiant ? Il y a un risque que les demandes arrivent dans le désordre et définissent le mauvais état. C'est donc juste quelque chose à garder à l'esprit. Si la récupération de données se trouve dans le composant lui-même, vous pouvez avoir une fonction de nettoyage d'effet qui définit un indicateur « ignorer » pour empêcher un setState de la réponse. (Bien sûr, déplacer des données vers un cache externe est souvent une meilleure solution - c'est aussi ainsi que Suspense fonctionnera.)

Faire le même "journal" (je suppose que cela pourrait être une soumission de formulaire) dans un gestionnaire d'événements semble plus approprié.

@gaearon le problème que je vois est que le faire dans le gestionnaire d'événements signifie que l'effet secondaire se produit avant la validation de la mise à jour. Il n'y a pas de garantie stricte que le composant sera restitué avec succès à la suite de cet événement, donc le faire dans le gestionnaire d'événements peut être prématuré.

Par exemple, si je souhaite enregistrer que l'utilisateur a soumis avec succès une nouvelle requête de recherche et affiche les résultats. Si quelque chose ne va pas et que le composant se déclenche, je ne voudrais pas que cet événement de journal se produise.

Ensuite, il y a le cas où cet effet secondaire peut être asynchrone, donc l'utilisation de useEffect vous donne la fonction de nettoyage.

Il y a aussi le problème de useReducer , où la valeur que je souhaiterais peut-être enregistrer ne sera pas disponible dans le gestionnaire d'événements. Mais je pense que c'est déjà sur votre radar

Dans les deux cas, l'approche que vous recommandez est probablement suffisante. Stockez l'état composé sous une forme où vous pouvez toujours accéder aux valeurs individuelles qu'il compose.

J'ai un crochet pratique pour envelopper les fonctions avec des paramètres supplémentaires et les transmettre. Cela ressemble à ceci :

function useBoundCallback(fn, ...bound) {
  return useCallback((...args) => fn(...bound, ...args), [fn, ...bound]);
}

(c'est en fait un peu plus compliqué car il a quelques fonctionnalités supplémentaires mais ce qui précède montre toujours le problème pertinent).

Le cas d'utilisation est lorsqu'un composant doit transmettre une propriété à un enfant et souhaite que l'enfant ait un moyen de modifier cette propriété spécifique. Par exemple, considérons une liste où chaque élément a une option d'édition. L'objet parent passe une valeur à chaque enfant et une fonction de rappel à appeler s'il souhaite modifier la valeur. L'enfant ne sait pas quel ID d'élément il affiche, donc le parent doit modifier le rappel pour inclure ce paramètre. Cela pourrait être imbriqué à une profondeur arbitraire.

Un exemple simplifié de ce flux est présenté ici : https://codesandbox.io/s/vvv36834k5 (en cliquant sur « Go ! » affiche un journal de la console qui inclut le chemin des composants)


Le problème est que j'obtiens 2 erreurs de linter avec cette règle :

React Hook (X) a une dépendance manquante : 'bound'. Soit l'inclure, soit supprimer le tableau de dépendances

React Hook (X) a un élément spread dans son tableau de dépendances. Cela signifie que nous ne pouvons pas vérifier statiquement si vous avez passé les bonnes dépendances

Le changer pour utiliser une liste de paramètres (c'est-à-dire ne pas utiliser l'opérateur spread) détruirait la mémorisation, car la liste est créée à chaque invocation même si les paramètres sont identiques.


Les pensées:

  • Vaut-il la peine d'afficher la première erreur lorsque la seconde s'applique également ?
  • y a-t-il un moyen de désactiver cette règle spécifiquement pour les endroits où l'opérateur de propagation est utilisé ?
  • si la seule utilisation d'une variable à l'intérieur d'une fonction est avec l'opérateur spread, il est sûr d'utiliser l'opérateur spread dans les dépendances, et puisque la règle le détecte déjà, elle devrait sûrement l'autoriser. Je me rends compte qu'il existe des cas plus complexes qui sont plus difficiles à résoudre avec l'analyse statique, mais cela semble être une victoire facile pour une utilisation relativement courante de la propagation.

Salut @gaearon , je viens de

Accessing 'myRef.current' during the effect cleanup will likely read a different ref value because by this time React has already updated the ref. If this ref is managed by React, store 'myRef.current' in a variable inside the effect itself and refer to that variable from the cleanup function.

Je trouve ce message un peu confus. Je suppose qu'il essaie de m'avertir que la valeur actuelle de référence au nettoyage peut être différente de la valeur au corps de l'effet. Droit?
Si tel est le cas, et en étant conscient, est-il sûr/légitime d'ignorer cet avertissement ?

Mon cas, si intéressant : CodeSandbox
Contexte : certains hooks de récupération de données personnalisés. J'utilise un compteur dans une référence pour empêcher à la fois les conditions de concurrence et les mises à jour sur les composants non montés.

Je pense que je pourrais contourner cet avertissement en masquant la référence lue dans une fonction ou en créant une autre référence booléenne pour le cas de nettoyage. Mais je trouve inutilement verbeux si je peux simplement ignorer cet avertissement.

@aweary

Par exemple, si je souhaite enregistrer que l'utilisateur a soumis avec succès une nouvelle requête de recherche et affiche les résultats. Si quelque chose ne va pas et que le composant se déclenche, je ne voudrais pas que cet événement de journal se produise.

Oui, cela ressemble à un cas d'utilisation de niche. Je pense que lorsque la plupart des gens veulent des "effets secondaires", ils veulent dire la soumission du formulaire lui-même - pas le fait que vous ayez consulté un formulaire soumis. Dans ce cas, la solution que j'ai fournie semble bonne.

@davidje13

Vaut-il la peine d'afficher la première erreur lorsque la seconde s'applique également ?

Veuillez déposer un nouveau problème pour des propositions visant à modifier la règle sur les peluches.

y a-t-il un moyen de désactiver cette règle spécifiquement pour les endroits où l'opérateur de propagation est utilisé ?

Vous pouvez toujours // eslint-disable-next-line react-hooks/exhaustive-deps si vous pensez savoir ce que vous faites.

si la seule utilisation d'une variable à l'intérieur d'une fonction est avec l'opérateur spread, il est sûr d'utiliser l'opérateur spread dans les dépendances, et puisque la règle le détecte déjà, elle devrait sûrement l'autoriser.

Déposez un nouveau problème s'il vous plaît.

@CarlosGines

Je trouve ce message un peu confus. Je suppose qu'il essaie de m'avertir que la valeur actuelle de référence au nettoyage peut être différente de la valeur au corps de l'effet. Droit?

Oui.

Si tel est le cas, et en étant conscient, est-il sûr/légitime d'ignorer cet avertissement ?

Ummm .. pas si cela conduit à un bug. ??

Contexte : certains hooks de récupération de données personnalisés. J'utilise un compteur dans une référence pour empêcher à la fois les conditions de concurrence et les mises à jour sur les composants non montés.

Ouais, peut-être que ce cas d'utilisation est légitime. Déposer un nouveau problème pour discuter svp ?

Je vais verrouiller ce problème car nous avons reçu suffisamment de commentaires et il a été intégré.

Questions et réponses courantes : https://github.com/facebook/react/issues/14920#issuecomment -471070149

Si vous voulez une plongée profonde sur useEffect et les dépendances, c'est ici : https://overreacted.io/a-complete-guide-to-useeffect/

Nous ajouterons bientôt plus de choses aux documents.

Si vous souhaitez modifier quelque chose dans la règle ou si vous n'êtes pas sûr que votre cas est légitime, déposez un nouveau problème.

Cette page vous a été utile?
0 / 5 - 0 notes