J'essaie les nouveaux Hooks React et j'ai un composant Clock avec un compteur qui est censé augmenter chaque seconde. Cependant, la valeur n'augmente pas au-delà de un.
function Clock() {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="app"></div>
la source
useEffect
La fonction n'est évaluée qu'une seule fois lors du montage du composant lorsqu'une liste d'entrée vide est fournie.Une alternative à
setInterval
est de définir un nouvel intervalle àsetTimeout
chaque fois que l'état est mis à jour:const [time, setTime] = React.useState(0); React.useEffect(() => { const timer = setTimeout(() => { setTime(time + 1); }, 1000); return () => { clearTimeout(timer); }; }, [time]);
L'impact sur les performances de
setTimeout
est insignifiant et peut être généralement ignoré. À moins que le composant est sensible au temps au point où les délais d' attente nouvellement créées provoquent des effets indésirables, à la foissetInterval
et lessetTimeout
approches sont acceptables.la source
Comme d'autres l'ont souligné, le problème est qu'il
useState
n'est appelé qu'une seule fois (car il sertdeps = []
à configurer l'intervalle:React.useEffect(() => { const timer = window.setInterval(() => { setTime(time + 1); }, 1000); return () => window.clearInterval(timer); }, []);
Ensuite, à chaque fois qu'il
setInterval
coche, il appellerasetTime(time + 1)
, maistime
conservera toujours la valeur qu'il avait initialement lorsque lesetInterval
rappel (fermeture) a été défini.Vous pouvez utiliser la forme alternative de
useState
's setter et fournir un rappel plutôt que la valeur réelle que vous souhaitez définir (comme avecsetState
):setTime(prevTime => prevTime + 1);
Mais je vous encourage à créer votre propre
useInterval
hook afin que vous puissiez DRY et simplifier votre code en utilisant de manièresetInterval
déclarative , comme le suggère Dan Abramov ici dans Making setInterval Declarative with React Hooks :function useInterval(callback, delay) { const intervalRef = React.useRef(); const callbackRef = React.useRef(callback); // Remember the latest callback: // // Without this, if you change the callback, when setInterval ticks again, it // will still call your old callback. // // If you add `callback` to useEffect's deps, it will work fine but the // interval will be reset. React.useEffect(() => { callbackRef.current = callback; }, [callback]); // Set up the interval: React.useEffect(() => { if (typeof delay === 'number') { intervalRef.current = window.setInterval(() => callbackRef.current(), delay); // Clear interval if the components is unmounted or the delay changes: return () => window.clearInterval(intervalRef.current); } }, [delay]); // Returns a ref to the interval ID in case you want to clear it manually: return intervalRef; } const Clock = () => { const [time, setTime] = React.useState(0); const [isPaused, setPaused] = React.useState(false); const intervalRef = useInterval(() => { if (time < 10) { setTime(time + 1); } else { window.clearInterval(intervalRef.current); } }, isPaused ? null : 1000); return (<React.Fragment> <button onClick={ () => setPaused(prevIsPaused => !prevIsPaused) } disabled={ time === 10 }> { isPaused ? 'RESUME ⏳' : 'PAUSE 🚧' } </button> <p>{ time.toString().padStart(2, '0') }/10 sec.</p> <p>setInterval { time === 10 ? 'stopped.' : 'running...' }</p> </React.Fragment>); } ReactDOM.render(<Clock />, document.querySelector('#app'));
body, button { font-family: monospace; } body, p { margin: 0; } p + p { margin-top: 8px; } #app { display: flex; flex-direction: column; align-items: center; min-height: 100vh; } button { margin: 32px 0; padding: 8px; border: 2px solid black; background: transparent; cursor: pointer; border-radius: 2px; }
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script> <div id="app"></div>
En plus de produire un code plus simple et plus propre, cela vous permet de suspendre (et d'effacer) automatiquement l'intervalle en passant
delay = null
et renvoie également l'ID d'intervalle, au cas où vous voudriez l'annuler vous-même manuellement (ce n'est pas couvert dans les articles de Dan).En fait, cela pourrait également être amélioré afin qu'il ne redémarre pas
delay
lorsqu'il est réactivé, mais je suppose que pour la plupart des cas d'utilisation, cela suffit.Si vous recherchez une réponse similaire pour
setTimeout
plutôt quesetInterval
, consultez ceci: https://stackoverflow.com/a/59274757/3723993 .Vous pouvez également trouver la version déclarative de
setTimeout
andsetInterval
,useTimeout
etuseInterval
, plus unuseThrottledCallback
hook personnalisé écrit en TypeScript dans https://gist.github.com/Danziger/336e75b6675223ad805a88c2dfdcfd4a .la source
Une solution alternative serait d'utiliser
useReducer
, car il sera toujours passé l'état actuel.function Clock() { const [time, dispatch] = React.useReducer((state = 0, action) => { if (action.type === 'add') return state + 1 return state }); React.useEffect(() => { const timer = window.setInterval(() => { dispatch({ type: 'add' }); }, 1000); return () => { window.clearInterval(timer); }; }, []); return ( <div>Seconds: {time}</div> ); } ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script> <div id="app"></div>
la source
useEffect
ici est-il appelé plusieurs fois pour mettre à jour l'heure, alors que le tableau des dépendances est vide, ce qui signifie que leuseEffect
ne doit être appelé que la première fois que le composant / l'application est rendu?useEffect
n'est appelée qu'une seule fois, lorsque le composant est effectivement rendu pour la première fois. Mais à l'intérieur, il y a unsetInterval
qui se charge de changer l'heure de façon régulière. Je vous suggère de lire un peu sursetInterval
, les choses devraient être plus claires après ça! developer.mozilla.org/en-US/docs/Web/API/…Faites comme ci-dessous, cela fonctionne bien.
const [count , setCount] = useState(0); async function increment(count,value) { await setCount(count => count + 1); } //call increment function increment(count);
la source
count => count + 1
rappel a fonctionné pour moi, merci!Ces solutions ne fonctionnent pas pour moi car j'ai besoin d'obtenir la variable et de faire certaines choses, pas seulement de la mettre à jour.
J'obtiens une solution de contournement pour obtenir la valeur mise à jour du crochet avec une promesse
Par exemple:
async function getCurrentHookValue(setHookFunction) { return new Promise((resolve) => { setHookFunction(prev => { resolve(prev) return prev; }) }) }
Avec cela, je peux obtenir la valeur à l'intérieur de la fonction setInterval comme ceci
let dateFrom = await getCurrentHackValue(setSelectedDateFrom);
la source
Dites à React re-render lorsque le temps a changé. se désengager
function Clock() { const [time, setTime] = React.useState(0); React.useEffect(() => { const timer = window.setInterval(() => { setTime(time + 1); }, 1000); return () => { window.clearInterval(timer); }; }, [time]); return ( <div>Seconds: {time}</div> ); } ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script> <div id="app"></div>
la source
count
changement.setTimeout()
c'est préféré comme le souligne Estus