Maison > Article > interface Web > Réagir : fermeture obsolète
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.
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é.
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 :
Dans l'image suivante, l'état initial de l'interface utilisateur de l'application est affiché, avec le compteur à zéro.
Nous allons simuler la fermeture du compteur en trois étapes :
Après 5 secondes, la valeur du compteur est 2.
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 (); } export default App;Closure demo
Counter value: {counter}
Follow the istructions to create a closure of the state variable counter
- Set the counter to preferred value
- Start a timeout and wait for {timeOutInSeconds} to increment the counter (current value is {counter})
- Increment by 10 the counter before the timeout
{ renderLogs() }
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)
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!