Wie andere bereits betont haben, besteht das Problem darin, dass useStatenur einmal aufgerufen wird ( deps = []um das Intervall festzulegen:
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => window.clearInterval(timer);
}, []);
Jedes Mal, wenn ein setIntervalHäkchen gesetzt ist, wird tatsächlich aufgerufen setTime(time + 1), es timewird jedoch immer der Wert beibehalten, den es ursprünglich hatte, als der setIntervalRückruf (Abschluss) definiert wurde.
Sie können die alternative Form des useStateSetters verwenden und einen Rückruf anstelle des tatsächlichen Werts bereitstellen, den Sie einstellen möchten (genau wie bei setState):
setTime(prevTime => prevTime + 1);
Aber ich würde Sie ermutigen, Ihren eigenen useIntervalHook zu erstellen, damit Sie Ihren Code durch setInterval deklarative Verwendung TROCKNEN und vereinfachen können , wie Dan Abramov hier in Making setInterval Declarative with React Hooks vorschlägt :
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/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>
Abgesehen davon, dass Sie einfacheren und saubereren Code erstellen, können Sie das Intervall automatisch anhalten (und löschen), indem Sie es übergeben, delay = nullund die Intervall-ID zurückgeben, falls Sie es selbst manuell abbrechen möchten (dies wird in Dans Beiträgen nicht behandelt).
Eigentlich könnte dies auch verbessert werden, damit es nicht neu delaygestartet wird, wenn es nicht angehalten ist, aber ich denke, für die meisten Anwendungsfälle ist dies gut genug.
Wenn Sie nach einer ähnlichen Antwort suchen setTimeoutund nicht nach setInterval, lesen Sie diese: https://stackoverflow.com/a/59274757/3723993 .
Sie können auch deklarative Version finden setTimeoutund setInterval, useTimeoutund useInterval, sowie ein benutzerdefinierter useThrottledCallbackin in Typoskript geschrieben Haken https://gist.github.com/Danziger/336e75b6675223ad805a88c2dfdcfd4a .