Réagir les méthodes du cycle de vie aux hooks

Dec 16 2020

J'ai un blog simple avec des articles. Et je veux réécrire les classes en composants fonctionnels et en hooks. Maintenant, j'ai cette logique dans les méthodes de cycle de vie de ma page avec un formulaire d'édition / d'ajout: cela fonctionne bien.

componentDidUpdate(prevProps, prevState) {
 if (this.props.match.params.id !== prevProps.match.params.id) {
    if (prevProps.match.params.id) {
        this.props.onUnload();
      }

      this.id = this.props.match.params.id;
        if (this.id) {
            return this.props.onLoad(userService.articles.get(this.id));
        }
        this.props.onLoad(null);
   }
   this.isLoading = false;
}

componentDidMount() {
  if (this.id) {
    this.isLoading = true;
    return this.props.onLoad(userService.articles.get(this.id));
  }
  this.isLoading = false;
  this.props.onLoad(null);
}
   
componentWillUnmount() {
   this.props.onUnload();
}
   
shouldComponentUpdate(newProps, newState) {
   if (this.props.match.params.id !== newProps.match.params.id) {
      this.isLoading = true;
   }
   return true;
}

J'ai tout réécrit en crochets comme ça:

//componentDidMount
  useEffect(() => {
    if (id) {
      setIsloading(true);
      return props.onLoad(userService.articles.get(id));
    }
    setIsloading(false);
    props.onLoad(null);
  }, []);

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

  //componentDidUpdate
  useEffect(() => {
    //const id = props.match.params.id;
    if (id !== prevId.current) {
      if (prevId.current) {
        props.onUnload();
      }
      if (id) {
        return props.onLoad(userService.articles.get(id));
      }
      props.onLoad(null);
    }
    setIsloading(false);
  });

  //componentWillUnmount
  useEffect(() => {
     return props.onUnload();
  }, []);

  1. J'ai eu une erreur - "Trop de rediffusions." à codesandbox code complet: codesandbox

C'est étrange, mais sur localhost, il n'y a pas d'erreur "Trop de rediffusions".

  1. Je ne sais pas quoi faire avec ma méthode de classe "shouldComponentUpdate" comment la réécrire en hooks. J'ai essayé «mémo» mais je ne sais pas comment écrire dans ce cas.

  2. Et de toute façon, il me manque quelque chose, car tout ne fonctionnera pas - il ne met pas à jour correctement les champs du formulaire.

Si vous avez une bonne connaissance des hooks de réaction, veuillez aider ou donner des conseils - comment y remédier?

Réponses

3 Mhmdrz_A Dec 18 2020 at 21:24

L'effet sans dépendance est à l'origine "Too many re-renders.": il s'exécute après chaque rendu puis il appelle setIsLoadingà mettre à jour l'état ( loading) qui provoquera le re-rendu du composant, qui exécutera le à effectnouveau et le setStatesera appelé à nouveau effectet ainsi de suite ...

//componentDidUpdate
  useEffect(() => {
    //const id = props.match.params.id;
    if (id !== prevId.current) {
      if (prevId.current) {
        props.onUnload();
      }
      if (id) {
        return props.onLoad(userService.articles.get(id));
      }
      props.onLoad(null);
    }
    setIsloading(false);
  })

pour résoudre le problème, supprimez-le setIsLoadingou ajoutez-le en IsLoadingtant que dépendance.

//componentDidUpdate
  useEffect(() => {
    ...
    setIsloading(false);
  },[isLoading]);

vous pouvez également fusionner deux effets de montage en un seul comme celui-ci (le vôtre fonctionne également, mais je pense que c'est préférable d'un point de vue stylistique):

//componentDidMount
  useEffect(() => {
    if (id) {
      setIsloading(true);
      return props.onLoad(userService.articles.get(id));
    }
    setIsloading(false);
    props.onLoad(null);

    //componentWillUnmount
    return function () {
      props.onUnload()
    }
  }, []);

pour la deuxième puce: à propos de la réécriture de votre composant shouldComponentUpdate; Je dois d'abord souligner que votre existence shouldComponentUpdaten'est pas raisonnable, puisque vous revenez toujours trueet que vous ne l'utilisez que pour déclencher un loadingétat; et il n'est pas éligible pour être écrit avec React.memo (qui est ~ l'équivalent des shouldComponentUpdatecomposants en classe); vous n'avez donc besoin que de quelque chose à exécuter à chaque changement d'accessoires pour déterminer l' loadingétat et pour cela, vous pouvez utiliser un effet avec propscomme dépendance comme ceci:

//manually keeping old props id
const prevPropsId = useRef();

useEffect(() => {
   if (prevPropsId.current !== props.match.params.id) {
      setIsLoading(true);
   }
   prevPropsId.current = props.match.params.id;
 }, [props.match.params.id]);
// this hook only run if `props.match.params.id` change 

sur la base de ma réalisation, cela fera le travail, (même si vous avez du mal à comprendre pourquoi vous écrivez la logique de cette façon en premier lieu) et si cela ne va pas bien, vous pouvez ajuster un peu la logique pour correspondre à vos besoins, vous obtenez le idée comment fonctionne l'effet de changement d'accessoires. vous devez également gérer typeErrorau cas où id, paramsou matchn'existerait pas et le chaînage facultatif peut être un outil pratique ici ->props.match?.params?.id

2 deckele Dec 23 2020 at 23:26

Si vous manquez les anciens hooks de cycle de vie, vous pouvez toujours les recréer en tant que hooks personnalisés:

function useComponentDidMount(effect) {
  useEffect(() => {
    effect();
  }, []);
}
function useComponentWillUnmount(effect) {
  useEffect(() => {
    return effect;
  }, []);
}
function useComponentDidUpdate(effect, dependencies) {
  const hasMountedRef = useRef(false);
  const prevDependenciesRef = useRef(null)
  useEffect(() => {
    // don't run on first render, only on subsequent changes
    if (!hasMountedRef.current) {
      hasMountedRef.current = true;
      prevDependenciesRef.current = dependencies;
      return;
    }
    // run effect with previous dependencies, like in componentDidUpdate!
    effect(...prevDependenciesRef.current || []);
    prevDependenciesRef.current = dependencies;
  }, dependencies)
}

Utilisation (votre exemple remanié):

  //componentDidMount
  useComponentDidMount(() => {
    if (id) {
      setIsloading(true);
      props.onLoad(userService.articles.get(id));
      return;
    }
    setIsloading(false);
    props.onLoad(null);
  });

  //componentDidUpdate
  useComponentDidUpdate((prevId) => {
    if (prevId) {
      props.onUnload();
    }
    if (id) {
      props.onLoad(userService.articles.get(id));
      return;
    }
    props.onLoad(null);
    setIsloading(false);
  }, [id]);

  //componentWillUnmount
  useComponentWillUnmount(() => {
     props.onUnload();
  });