使用间隔
useInterval
来自Dan Abramov 的这篇博文(2019 年):
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
一个潜在的错误
可以在提交阶段和调用之间调用间隔回调useEffect
,从而导致调用旧的(因此不是最新的)回调。换句话说,这可能是执行顺序:
- 渲染阶段- 的新值
callback
。 - 提交阶段- 提交给 DOM 的状态。
- 使用布局效果
- 间隔回调- using
savedCallback.current()
,与callback
. - 使用效果-
savedCallback.current = callback;
React 的生命周期
为了进一步说明这一点,这里有一张图表,显示了带有钩子的 React 生命周期:
虚线表示异步流(事件循环已释放),您可以在这些点进行间隔回调调用。
但是请注意,Render
和React updates DOM
(提交阶段)之间的虚线很可能是一个错误。正如此代码和框所示,您只能在useLayoutEffect
or之后useEffect
(但不能在渲染阶段之后)调用间隔回调。
所以你可以在 3 个地方设置回调:
- 渲染 - 不正确,因为状态更改尚未提交到 DOM。
useLayoutEffect
- 正确,因为状态更改已提交到 DOM。useEffect
- 不正确,因为旧的间隔回调可能会在此之前触发(在布局效果之后)。
演示
这个错误在这个codeandebox 中得到了证明。重现:
- 将鼠标移到灰色 div 上 - 这将导致带有新
callback
参考的新渲染。 - 通常,您会看到在少于 2000 次鼠标移动时抛出的错误。
- 间隔设置为 50 毫秒,因此您需要一点运气才能在渲染和效果阶段之间触发。
用例
演示显示当前的回调值可能与useEffect
正常的不同,但真正的问题是其中哪一个是“正确的”?
考虑这个代码:
const [size, setSize] = React.useState();
const onInterval = () => {
console.log(size)
}
useInterval(onInterval, 100);
如果onInterval
在提交阶段之后但之前调用useEffect
,它将打印错误的值。