Comme d'autres l'ont souligné, le problème est qu'il useStaten'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 setIntervalcoche, il appellera setTime(time + 1), mais timeconservera toujours la valeur qu'il avait initialement lorsque le setIntervalrappel (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 useIntervalhook 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 = nullet 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 delaylorsqu'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 setTimeoutplutôt que setInterval, consultez ceci: https://stackoverflow.com/a/59274757/3723993 .
Vous pouvez également trouver la version déclarative de setTimeoutand setInterval, useTimeoutet useInterval, plus un useThrottledCallbackhook personnalisé écrit en TypeScript dans https://gist.github.com/Danziger/336e75b6675223ad805a88c2dfdcfd4a .