Wie andere bereits betont haben, besteht das Problem darin, dass useState
nur 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 setInterval
Häkchen gesetzt ist, wird tatsächlich aufgerufen setTime(time + 1)
, es time
wird jedoch immer der Wert beibehalten, den es ursprünglich hatte, als der setInterval
Rückruf (Abschluss) definiert wurde.
Sie können die alternative Form des useState
Setters 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 useInterval
Hook 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 = null
und 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 delay
gestartet 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 setTimeout
und nicht nach setInterval
, lesen Sie diese: https://stackoverflow.com/a/59274757/3723993 .
Sie können auch deklarative Version finden setTimeout
und setInterval
, useTimeout
und useInterval
, sowie ein benutzerdefinierter useThrottledCallback
in in Typoskript geschrieben Haken https://gist.github.com/Danziger/336e75b6675223ad805a88c2dfdcfd4a .