Comme d'autres l'ont souligné, le problème est qu'il useState
n'est appelé qu'une seule fois (car il sert deps = []
à 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 appellera setTime(time + 1)
, mais time
conservera toujours la valeur qu'il avait initialement lorsque le setInterval
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 avec setState
):
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ère setInterval
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);
React.useEffect(() => {
callbackRef.current = callback;
}, [callback]);
React.useEffect(() => {
if (typeof delay === 'number') {
intervalRef.current = window.setInterval(() => callbackRef.current(), delay);
return () => window.clearInterval(intervalRef.current);
}
}, [delay]);
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/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/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 que setInterval
, consultez ceci: https://stackoverflow.com/a/59274757/3723993 .
Vous pouvez également trouver la version déclarative de setTimeout
and setInterval
, useTimeout
et useInterval
, plus un useThrottledCallback
hook personnalisé écrit en TypeScript dans https://gist.github.com/Danziger/336e75b6675223ad805a88c2dfdcfd4a .