J'ai écrit un simple chronomètre dans React.
Afin de rendre un nouveau nombre toutes les secondes, j'ai utilisé setInterval:
i is: 0 in incrementNumber, i is: 1 i is: 0 in incrementNumber, i is: 2 i is: 0 in incrementNumber, i is: 3 i is: 0 in incrementNumber, i is: 4 i is: 0 in incrementNumber, i is: 5 i is: 0
La question est: pourquoi ce chronomètre fonctionne-t-il comme prévu?
Puisque chaque seconde je re-rend le composant, la ligne let i = 0
est exécutée à nouveau, donc je m'attends à ce que
la valeur renvoyée par incrementNumber
sera toujours 1 (0 + 1)
Mais à la place, la valeur renvoyée est incrémentée: 1,2,3,4,5.
Voici la sortie de la console:
import React, {useEffect, useState, useRef} from 'react'; const MAX_TIMER = 5; export default function StopWatch() { const [timer, setTimer] = useState(0); const tickIntervalId = useRef(null); const tickTimer = (getTimerTimeFn) => { const number = getTimerTimeFn(); setTimer(number); if(number === MAX_TIMER) { clearInterval(tickIntervalId.current); } } let i = 0; console.log('i is: ', i) // i value is 0 every render const incrementNumber = () => { i += 1; console.log('in incrementNumber, i is: ', i); // i value is incrementing every render: 1, 2, 3, 4, 5. how? return i; } useEffect(() => { tickIntervalId.current = setInterval(tickTimer, 1000, incrementNumber); }, []) return ( <div> {timer} </div> ); }
4 Réponses :
Vous devez connaître l'environnement lexical et le contexte d'exécution et le fonctionnement de l'oscilloscope
Déjà discuté ici
exemple presque similaire
https://stackoverflow.com/a/19123476
Je pense que c'est une question de portée, si vous donnez console.log (i) dans useEffect, nous pouvons voir qu'il ne se connecte qu'au premier rendu. Comme si le let i à l'intérieur de useEffect était différent de celui du composant. Même avec le nouveau rendu, l'état dans useEffect demeure.
useEffect(() => { console.log("i in effect: ", i) tickIntervalId.current = setInterval(tickTimer, 1000, incrementNumber); }, []);
Merci d'avoir répondu. Cependant, la raison pour laquelle il n'est imprimé qu'une seule fois, c'est parce que cet useEffect ne s'exécute qu'une seule fois lorsque le composant se monte (à cause du tableau vide que j'ai fourni, c'est comme ComponentDidMount)
Voici un petit exemple pour illustrer le problème de la portée lexicale.
function myFn() { let i = 0; // `i` is local to myFn() (has local scope) console.log(`myFn i == ${i}`); return (caller) => { i++; console.log(`${caller} : i == ${i}`); } } let a = myFn(); // 'myFn i == 0' a('a'); // 'a : i == 1' let b = myFn(); // 'myFn i == 0' b('b'); // 'b : i == 1' b('b'); // 'b : i == 2' b('b'); // 'b : i == 3' a('a'); // 'a : i == 2'
@Ido
Voici un lien vers quelque chose qui a plus de sens: j'utilise un objet pour que vous puissiez voir où la variable a été stockée. J'ai également voté pour toutes les autres réponses car elles sont correctes. C'est un problème de portée, pas un problème de réaction.
https: // codesandbox .io / s / material-demo-wj0pm
import React, { useEffect, useState, useRef } from "react"; const MAX_TIMER = 5; let renderCount = 0; export default function StopWatch() { const [timer, setTimer] = useState({}); const tickIntervalId = useRef(null); const tickTimer = getTimerTimeFn => { const number = getTimerTimeFn(); setTimer({...number}); if (number === MAX_TIMER) { clearInterval(tickIntervalId.current); } }; var iObject = { i: 0, inRender: renderCount, }; renderCount++; console.log("i is: ", JSON.stringify(iObject) ); // i value is 0 every render const incrementNumber = () => { iObject.i += 1; console.log("in incrementNumber, i is: ", JSON.stringify(iObject)); // i value is incrementing every render: 1, 2, 3, 4, 5. how? console.log("------------------------"); return iObject; //Not a copy }; useEffect(() => { tickIntervalId.current = setInterval(tickTimer, 1000, incrementNumber); }, []); return <div>{JSON.stringify(timer)}</div>; }
Déplacez les déclarations
i
etincrementNumber
vers la portée globale.Le code fonctionne, comme vous pouvez le voir dans le bac à sable de code que j'ai joint. La question n'est pas de savoir comment le faire fonctionner.
@Indiquez-vous la variable
i
en dehors de la portée de la fonction, donc à chaque appel, elle s'incrémente. Si vous déclariezi
dans la fonctionincrementNumber
, il se réinitialiserait toujours à zéro. Est-ce votre question?i
est effectivement membre deStopWatch ()
@ DanielB.Chapman Ce n'est pas la question, la question est de savoir comment est-il possible que
i
continue d'augmenter à l'intérieur de la fonctionincrementNumber
, même sii
non stocké dans l'état, et vous pouvez même voir dans la sortie de la console quei
est mis à 0 à chaque rendu.@Ido C'est une question de portée lexicale, c'est pourquoi déplacer
i
etincrementNumber
vers la portée globale le casse.