React Hooks中setInterval内调用setCount触发两次渲染的原因
问题原因
这个现象是React 18+ 版本开发环境下,严格模式(StrictMode)的副作用校验机制触发的,属于框架的预期行为,不是代码逻辑bug。
具体执行逻辑拆解
React 18 对严格模式做了升级,开发环境下每个组件首次挂载时,会主动执行一轮「挂载 → 模拟卸载 → 重新挂载」的流程,目的是提前暴露副作用未正确清理的隐蔽问题,这个重复执行逻辑仅在开发环境生效,生产环境打包后不会出现。
对应你贴的代码,完整执行流程如下:
- 组件首次挂载阶段
- 组件函数执行,打印
useEffect before 60、useEffect after 60 - 执行空依赖的useEffect回调,打印
useEffect 60,注册第一个setInterval定时器,这个定时器的闭包捕获的count是初始值60 - React触发模拟卸载,执行useEffect返回的清理函数——但你写的清理函数是空逻辑,没有调用
clearInterval清除第一个定时器,导致这个定时器成了游离的残留副作用
- 组件函数执行,打印
- 组件模拟卸载后立即重挂载阶段
- 组件函数再次执行,此时count还没更新,依然打印
useEffect before 60、useEffect after 60 - 再次执行空依赖的useEffect回调,打印
useEffect 60,注册第二个setInterval定时器,闭包同样捕获初始值60
- 组件函数再次执行,此时count还没更新,依然打印
- 1秒后定时器触发阶段
- 之前残留的第一个定时器触发,打印
count 60,执行setCount(59) - 新注册的第二个定时器触发,同样打印
count 60,执行setCount(59) - 因为两次setCount传入的都是固定值59,React会对相同的state做跳过重渲染的优化,不会触发两次组件重渲染,但两个定时器回调确实都执行了,就是你看到日志重复的原因。
- 之前残留的第一个定时器触发,打印
修复方案
不要通过移除严格模式规避这个现象,严格模式能帮你提前排查很多潜在bug,正确做法是补全副作用的清理逻辑:
useEffect(() => { console.log("useEffect", count); const timeId = setInterval(() => { console.log("count", count); setCount(59); }, 1000); // 组件卸载/effect重执行前清除上一个定时器 return () => { clearInterval(timeId); }; }, []);
额外提一句:你现在的写法里,定时器闭包永远捕获的是首次渲染的count=60,如果后续要做倒计时等需要拿到最新state的逻辑,要么把对应state加入useEffect依赖数组,要么使用函数式更新写法setCount(prev => prev - 1),避免闭包陈旧值的问题。
内容的提问来源于stack exchange,提问作者2397667718qqcom
相关产品推荐
相关产品推荐

