为什么使用setInterval后React组件渲染3次而非预期的2次?
你的理解存在两个核心误区,对应问题的根本原因如下:
1. 空依赖useEffect的闭包陷阱
你给useEffect传的依赖项是空数组,意味着这个effect只会在组件首次挂载完成后执行一次。内部的setInterval回调捕获的是首次渲染时的count值(固定为0),所以不管后续组件怎么重渲染,每次执行setCount(count + 1)等价于执行setCount(1),并不会拿到最新的count值。
按照你的逻辑,确实只有第一次把count从0改成1的时候会触发重渲染,后续所有setCount(1)都不会修改状态,也不会触发重渲染,生产环境下这个逻辑是成立的,最终输出就是你预期的App:1、App:2。
2. React18开发环境严格模式的特殊逻辑
你用的是React18的开发环境,默认开启了严格模式,会有两个特殊逻辑导致输出和预期不符:
- 双重挂载:开发环境下严格模式会模拟「挂载→卸载→重新挂载」的流程,用来排查没有正确清理的副作用。你没有给useEffect写清理函数,第一次挂载启动的定时器在模拟卸载时不会被清除,第二次挂载又会启动第二个定时器,最终后台同时跑了两个间隔2秒的定时器。
- 日志抑制:严格模式下第二次挂载执行组件函数时,React会默认抑制组件内部的console.log输出,所以你看不到第二次挂载时
i=2的打印。
两个定时器都在非React上下文执行,不会走批量更新逻辑,加上setInterval本身存在执行时间漂移,两个定时器的触发时间逐渐错开,导致偶尔会出现状态变更的判断异常,触发多余的重渲染,所以你会看到App:3、App:5这样不符合预期的打印。
修复方案
- 解决闭包问题:要么给
useEffect添加count依赖,要么使用setCount的函数式更新写法拿到最新状态 - 清理定时器:在useEffect的返回函数中清除定时器,避免内存泄漏
// 修复后的useEffect写法(函数式更新不需要加count依赖) useEffect(() => { const timer = setInterval(() => setCount(prev => prev + 1), 2000); // 清理函数,组件卸载/effect重新执行前清除定时器 return () => clearInterval(timer); }, []);
如果不需要严格模式的检查,可以删除入口文件的<React.StrictMode>标签,开发环境也不会触发双重挂载逻辑。
内容的提问来源于stack exchange,提问作者macyou
相关产品推荐
相关产品推荐

