https://overreacted.io/making-setinterval-declarative-with-react-hooks/
这是Dan的一篇文章,详细阐述了如何在Hooks中使用setInterval定时器(每秒递增的计数器为例),由于提取精髓,因此略有删减。
我们在 effects 中直接使用定时器会重复启动然后清除,effects 在每次渲染时都会被调用,因此我们需要一个传递一个空数组来保证它只会被调用一次,此时获取 count 新值就会很困难。
最简单处理方式是使用 state callback :
setCount((count) => count + 1)
原因 effects 是一个闭包,一直引用了第一次渲染时 count 的值,虽然 state callback 可以帮助你获取新的 state 值,但问题来了无法获取新的 props 值。
使用 useRef 来保存新的 interval 并触发回调:
const savedCallback = useRef();
// 每次渲染后保存新的callback到ref中
useEffect(() => {
savedCallback.current = callback;
});
// 只执行一次,不会被重置,在渲染后读取回调并在 interval tick 中执行它
useEffect(() => {
function tick() {
savedCallback.current();
}
let id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);
由于我们将 callback 保存到了 ref 中,因此解决了这个问题。
如果我们想通过参数来控制它的行为,比如暂停,重启 interval 等,假设我们的设计 delay 参数为 null 时暂停 interval ,是数值时就启动 interval,该如何做?
我们只需要在 effects 中做一点小改动:
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
// 通过 null 来控制不创建 interval
其实通过这个 callback 案例来看,useReducer 也能解决这个问题,在 reducer 中我们可以访问到当前最新的 state 和 props ,本身 dispatch 也不会改变,所以我们可以从其中提取我们想要的。
最后结论:
我(Dan)希望这篇文章可以帮助你理解带有 setInterval() 等 API 的 Hooks 的相关常见问题、可以帮助你克服它们的模式、及享用建立在它们之上更具表达力的声明式 APIs 的甜蜜果实。