当组件重新渲染时,您可能不希望通过始终重新启动计时器来丢失setInterval
和之间的差异setTimeout
。这个小提琴显示了当其他代码也在运行时两者之间的漂移差异。(在较旧的浏览器/机器上——就像我最初回答这个问题时一样——你甚至不需要模拟大型计算就可以看到仅几秒钟后就开始发生明显的漂移。)
现在参考您的回答,Marco,setInterval
因为每次重新渲染组件时,无条件的效果都会处理和重新运行,所以完全失去了使用。因此,在您的第一个示例中,current
依赖项的使用会导致每次current
更改时(每次间隔运行时)处理并重新运行该效果。第二个做同样的事情,但实际上每次任何状态改变(导致重新渲染),这可能会导致一些意想不到的行为。一个工作的唯一原因是因为next()
导致状态改变。
考虑到您可能不关心确切的时间这一事实,setTimeout
以简单的方式使用current
和auto
vars 作为依赖项是最干净的。因此,要重新陈述您的部分答案,请执行以下操作:
useEffect(
() => {
if (!auto) return;
const interval = setTimeout(_ => {
next();
}, autoInterval);
return _ => clearTimeout(interval);
},
[auto, current]
);
一般来说,对于那些只是阅读这个答案并想要一种方法来做一个简单的计时器的人来说,这里的版本没有考虑到 OP 的原始代码,也没有考虑到他们需要一种独立启动和停止计时器的方法:
const [counter, setCounter] = useState(0);
useEffect(
() => {
const id= setTimeout(() => {
setCounter(counter + 1);
// You could also do `setCounter((count) => count + 1)` instead.
// If you did that, then you wouldn't need the dependency
// array argument to this `useEffect` call.
}, 1000);
return () => {
clearTimeout(id);
};
},
[counter],
);
但是,鉴于setTimeout
可以漂移超过setInterval
. 这是一种不使用 OP 代码的通用方法:
// Using refs:
const [counter, setCounter] = useState(30);
const r = useRef(null);
r.current = { counter, setCounter };
useEffect(
() => {
const id = setInterval(() => {
r.current.setCounter(r.current.counter + 1);
}, 1000);
return () => {
clearInterval(id);
};
},
[] // empty dependency array
);
// Using the function version of `setCounter` is cleaner:
const [counter, setCounter] = useState(30);
useEffect(
() => {
const id = setInterval(() => {
setCounter((count) => count + 1);
}, 1000);
return () => {
clearInterval(id);
};
},
[] // empty dependency array
);
这是上面发生的事情:
(第一个例子,使用 refs):要让setInterval
的回调始终引用当前可接受的版本,setCounter
我们需要一些可变状态。React 为我们提供了这个useRef
。该useRef
函数将返回一个具有current
属性的对象。然后我们可以将该属性(每次组件重新渲染时都会发生)设置为counter
和的当前版本setCounter
。
(第二个示例,使用功能性setCounter
):与第一个相同的想法,除了当我们使用 的函数版本时setCounter
,我们将可以访问计数的当前版本作为函数的第一个参数。无需使用 ref 来保持最新状态。
(两个例子,继续):然后,为了防止间隔在每次渲染时被处理,我们添加一个空的依赖数组作为第二个参数useEffect
。卸载组件时,该间隔仍将被清除。
注意:我曾经喜欢使用["once"]
作为我的依赖数组来表示我强制只设置一次这个效果。当时它的可读性很好,但我不再使用它有两个原因。首先,现在钩子得到了更广泛的理解,我们到处都看到了空数组。其次,它与非常流行的“钩子规则”linter 冲突,后者对依赖数组中的内容非常严格。
因此,将我们所知道的应用于 OP 的原始问题,您可以使用这样setInterval
的不太可能漂移的幻灯片:
// ... OP's implementation code including `autoInterval`,
// `auto`, and `next` goes above here ...
const r = useRef(null);
r.current = { next };
useEffect(
() => {
if (!auto) return;
const id = setInterval(() => {
r.current.next();
}, autoInterval);
return () => {
clearInterval(id);
};
},
[auto]
);