正如其他人指出的那样,问题在于useState
只调用一次(as deps = []
)来设置间隔:
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => window.clearInterval(timer);
}, []);
然后,每次setInterval
滴答声,它实际上都会调用setTime(time + 1)
,但time
将始终保持它最初在setInterval
定义回调(闭包)时的值。
您可以使用useState
's setter 的替代形式并提供回调而不是您要设置的实际值(就像 with 一样setState
):
setTime(prevTime => prevTime + 1);
但是我鼓励你创建自己的useInterval
钩子,这样你就可以通过使用setInterval
声明方式来干燥和简化你的代码,正如 Dan Abramov 在使用 React Hooks 制作 setInterval 声明中所建议的那样:
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>
除了生成更简单、更清晰的代码之外,这还允许您通过简单地传递来自动暂停(和清除)间隔,delay = null
并返回间隔 ID,以防您想自己手动取消它(Dan 的帖子中没有涉及)。
实际上,这也可以改进,以便在未暂停时不会重新启动delay
,但我想对于大多数用例来说这已经足够好了。
setTimeout
如果您正在为而不是寻找类似的答案setInterval
,请查看:https ://stackoverflow.com/a/59274757/3723993 。
您还可以在https://www.npmjs.com/package/@swyg/corresetTimeout
中找到and 和 and 的声明式版本,以及setInterval
一些useTimeout
用useInterval
TypeScript 编写的附加钩子。