web-dev-qa-db-fra.com

React hook useEffect tableau de dépendances

J'essaie d'enrouler ma tête autour de la nouvelle API de crochets de React. Plus précisément, j'essaie de construire le cas d'utilisation classique qui était autrefois le suivant:

componentDidUpdate(prevProps) {
    if (prevProps.foo !== this.props.foo) {
        // animate dom elements here...
        this.animateSomething(this.ref, this.props.onAnimationComplete);
    }
}

Maintenant, j'ai essayé de construire le même avec un composant fonction et useEffect, mais je ne sais pas comment le faire. Voici ce que j'ai essayé:

useEffect(() => {
    animateSomething(ref, props.onAnimationComplete);
}, [props.foo]);

De cette façon, l'effet n'est appelé que lorsque props.foo change. Et cela fonctionne - MAIS! Il semble que ce soit un anti-modèle puisque le eslint-plugin-react-hooks marque cela comme une erreur. Toutes les dépendances utilisées dans l'effet doivent être déclarées dans le tableau des dépendances. Cela signifie donc que je devrais faire ce qui suit:

useEffect(() => {
    animateSomething(ref, props.onAnimationComplete);
}, [props.foo, ref, props.onAnimationComplete]);

Cela ne conduit pas à l'erreur de peluchage MAIS, cela va totalement à l'encontre du but de niquement appeler l'effet lorsque props.foo changements. Je NE VEUX PAS qu'il soit appelé lorsque les autres accessoires ou la référence changent.

Maintenant, j'ai lu quelque chose sur l'utilisation de useCallback pour envelopper cela. Je l'ai essayé mais je ne suis pas allé plus loin.

Quelqu'un peut-il aider?

35
overdub60

Je recommanderais d'écrire ceci comme suit:

const previousFooRef = useRef(props.foo);

useEffect(() => {
    if (previousFooRef.current !== props.foo) {
       animateSomething(ref, props.onAnimationComplete);
       previousFooRef.current = props.foo;
    }
}, [props.foo, props.onAnimationComplete]);

Vous ne pouvez pas éviter la complexité d'avoir une condition à l'intérieur de l'effet, car sans elle, vous exécuterez votre animation sur la monture plutôt que juste au moment où props.foo changements. La condition vous permet également d'éviter d'animer lorsque des choses autres que props.foo changement.

En incluant props.onAnimationComplete dans le tableau des dépendances, vous évitez de désactiver la règle de lint qui vous garantit de ne pas introduire de futurs bogues liés aux dépendances manquantes.

Voici un exemple de travail:

Edit animate

19
Ryan Cogswell

Déplacez les valeurs, qui doivent être fraîches (pas périmées) dans le rappel mais ne doivent pas refire l'effet, vers refs:

const elementRef = useRef(); // Ex `ref` from the question
const animationCompleteRef = useRef();

animationCompleteRef.current = props.onAnimationComplete;

useEffect(() => {
    animateSomething(elementRef, animationCompleteRef.current);
}, [props.foo, elementRef, animationCompleteRef]);

Cela fonctionne car useRef la valeur de retour ne change pas sur les rendus.

0
Finesse

Supprimez le linter car il vous donne un mauvais conseil. React vous oblige à passer au second argument les valeurs qui (et seulement lesquelles) les changements doivent déclencher un effet de tir.

useEffect(() => {
    animateSomething(ref, props.onAnimationComplete);
}, [props.foo]); // eslint-disable-line react-hooks/exhaustive-deps

Cela conduit au même résultat que le solution de Ryan .

Je ne vois aucun problème à violer cette règle de linter. Contrairement à useCallback et useMemo, cela ne conduira pas à des erreurs dans le cas commun. Le contenu du deuxième argument est une logique de haut niveau.

Vous pouvez même appeler un effet lorsqu'une valeur étrangère change:

useEffect(() => {
    alert(`Hi ${props.name}, your score is changed`);
}, [props.score]);
0
Finesse