Maison  >  Article  >  interface Web  >  Réagir : fermeture obsolète

Réagir : fermeture obsolète

王林
王林original
2024-08-21 06:19:02469parcourir

Dans cet article, je vais montrer comment créer une fermeture dans une application React useState hook.

Je n'expliquerai pas ce qu'est une fermeture, car il existe de nombreuses ressources sur ce sujet et je ne veux pas être répétitif. Je conseille la lecture de cet article de @imranabdulmalik.

En bref, une fermeture est (de Mozilla) :

...la combinaison d'une fonction regroupée (ci-jointe) avec des références à son état environnant (l'environnement lexical). En d’autres termes, une fermeture vous donne accès à la portée d’une fonction externe à partir d’une fonction interne. En JavaScript, des fermetures sont créées à chaque fois qu'une fonction est créée, au moment de la création de la fonction.

Juste au cas où vous ne seriez pas familier avec le terme environnement lexical, vous pouvez lire cet article de @soumyadey ou bien celui-ci.

Le problème

Dans une application React, vous pouvez créer accidentellement une fermeture d'une variable appartenant à l'état du composant créé avec le hook useState. Lorsque cela arrive, vous êtes confronté à un problème de fermeture obsolète, c'est-à-dire lorsque vous faites référence à une ancienne valeur de l'État qui, entre-temps, a changé, et donc n'est plus d'actualité.

POC

J'ai créé une application Demo React dont le but principal est d'incrémenter un compteur (appartenant à l'état) qui peut être fermé lors d'une fermeture dans le rappel de la méthode setTimeout.

En bref, cette application peut :

  • Afficher la valeur du compteur
  • Incrémenter de 1 le compteur
  • Démarrez une minuterie pour incrémenter le compteur de 1 après cinq secondes.
  • Incrémenter de 10 le compteur

Dans l'image suivante, l'état initial de l'interface utilisateur de l'application est affiché, avec le compteur à zéro.

React: stale closure

Nous allons simuler la fermeture du compteur en trois étapes :

  1. Incrémentation de 1 le compteur

React: stale closure

  1. Démarrage de la minuterie pour incrémenter de 1 après cinq secondes

React: stale closure

  • Incrémentation de 10 avant le déclenchement du délai d'attente React: stale closure

Après 5 secondes, la valeur du compteur est 2.

React: stale closure

La valeur attendue du compteur devrait être 12, mais nous obtenons 2.

La raison pour laquelle cela se produit est que nous avons créé une fermeture du compteur dans le rappel passé à setTimeout et lorsque le délai d'attente est déclenché, nous réglons le compteur à partir de son ancienne valeur (c'était 1).

setTimeout(() => {
        setLogs((l) => [...l, `You closed counter with value: ${counter}\n and now I'll increment by one. Check the state`])
        setTimeoutInProgress(false)
        setStartTimeout(false)
        setCounter(counter + 1)
        setLogs((l) => [...l, `Did you create a closure of counter?`])

      }, timeOutInSeconds * 1000);

En suivant le code complet du composant de l'application.

function App() {
  const [counter, setCounter] = useState(0)
  const timeOutInSeconds: number = 5
  const [startTimeout, setStartTimeout] = useState(false)
  const [timeoutInProgress, setTimeoutInProgress] = useState(false)
  const [logs, setLogs] = useState>([])

  useEffect(() => {
    if (startTimeout && !timeoutInProgress) {
      setTimeoutInProgress(true)
      setLogs((l) => [...l, `Timeout scheduled in ${timeOutInSeconds} seconds`])
      setTimeout(() => {
        setLogs((l) => [...l, `You closed counter with value: ${counter}\n and now I'll increment by one. Check the state`])
        setTimeoutInProgress(false)
        setStartTimeout(false)
        setCounter(counter + 1)
        setLogs((l) => [...l, `Did you create a closure of counter?`])

      }, timeOutInSeconds * 1000);
    }
  }, [counter, startTimeout, timeoutInProgress])

  function renderLogs(): React.ReactNode {
    const listItems = logs.map((log, index) =>
      
  • {log}
  • ); return
      {listItems}
    ; } function updateCounter(value: number) { setCounter(value) setLogs([...logs, `The value of counter is now ${value}`]) } function reset() { setCounter(0) setLogs(["reset done!"]) } return (

    Closure demo


    Counter value: {counter}


    Follow the istructions to create a closure of the state variable counter

    1. Set the counter to preferred value
    2. Start a timeout and wait for {timeOutInSeconds} to increment the counter (current value is {counter})
    3. Increment by 10 the counter before the timeout

    { renderLogs() }
    ); } export default App;

    Solution

    La solution est basée sur l'utilisation du hook useRef qui permet de référencer une valeur qui n'est pas nécessaire au rendu.

    Nous ajoutons donc au composant App :

    const currentCounter = useRef(counter)
    

    Ensuite, nous modifierons le rappel de setTimeout comme indiqué ci-dessous :

    setTimeout(() => {
            setLogs((l) => [...l, `You closed counter with value: ${currentCounter.current}\n and now I'll increment by one. Check the state`])
            setTimeoutInProgress(false)
            setStartTimeout(false)
            setCounter(currentCounter.current + 1)
            setLogs((l) => [...l, `Did you create a closure of counter?`])
    
          }, timeOutInSeconds * 1000);
    

    Notre rappel doit lire la valeur du compteur car nous enregistrons la valeur actuelle avant de l'incrémenter.

    Dans le cas où vous n'avez pas besoin de lire la valeur, vous pouvez éviter la fermeture du compteur en utilisant simplement la notation fonctionnelle pour mettre à jour le compteur.

    seCounter(c => c + 1)
    

    Ressources

    • Dmitri Pavlutin Soyez conscient des fermetures obsolètes lors de l'utilisation de React Hooks
    • Imran Abdulmalik maîtrisant les fermetures en JavaScript : un guide complet
    • Portée lexicale Keyur Paralkar en JavaScript – Guide du débutant
    • Souvik Paul Fermetures obsolètes dans React
    • Soumya Dey Comprendre la portée lexicale et les fermetures en JavaScript
    • Stackoverflow de Subash Mahapatra

    Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

    Déclaration:
    Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn