React与原生JavaScript中setInterval的行为差异问题咨询
两段代码行为差异的核心原因
是闭包捕获的变量环境不同,本质是React函数组件的渲染机制和普通JS脚本执行逻辑的区别。
原生JS控制台代码行为说明
你在控制台运行的代码中,value是在同一个块级作用域下用let声明的变量,setInterval的回调函数捕获的是这个变量的引用。后续你直接修改这个变量的值,回调访问的始终是同一个引用对应的最新值,所以会打印更新后的3。
React组件代码行为说明
React函数组件的特点是:每次触发重新渲染,函数都会独立执行一次,生成完全独立的作用域,对应本次渲染的所有状态变量都是独立存在的。
你写的useEffect依赖项是空数组[],因此这个useEffect只会在组件第一次挂载时执行1次:
- 第一次渲染时
value的初始值为0,setInterval的回调捕获的是第一次渲染作用域下的value变量,值固定为0 - 后续你点击按钮调用
setValue触发组件重新渲染,只会生成新渲染作用域下的新value变量,和第一次渲染时闭包捕获的value没有任何关联
因此定时器的回调永远只会打印0。
常见解决方案
- 方案1:把
value添加到useEffect的依赖数组中
每次value变化时,useEffect会先执行清理函数销毁旧定时器,再创建新的定时器,新定时器的回调会捕获最新的value值:
useEffect(() => { const id = setInterval(() => console.log(value), 1000); return () => clearInterval(id); }, [value]);
- 方案2:用
useRef存储状态值useRef生成的对象在组件全生命周期内共享同一个引用,修改current属性不会触发重渲染,所有闭包都可以拿到最新的值:
function App() { const [value, setValue] = useState(0); const valueRef = useRef(value); // 每次value更新时同步更新ref的current值 useEffect(() => { valueRef.current = value; }, [value]); useEffect(() => { const id = setInterval(() => console.log(valueRef.current), 1000); return () => clearInterval(id); }, []); // 其余渲染逻辑不变 return ( <div className="App"> <p>{value}</p> <button onClick={() => setValue(value + 1)}>+</button> <button onClick={() => setValue(value - 1)}>-</button> </div> ); }
简化演示代码
你可以运行以下代码直观理解React函数组件的闭包机制:
// 模拟React组件多次渲染的逻辑 function simulateRender(currentValue) { // 每次渲染生成独立的作用域,对应独立的currentValue变量 setTimeout(() => { console.log(`当前渲染批次拿到的value:${currentValue}`); }, 1000); } simulateRender(0); // 第一次渲染,value=0 simulateRender(1); // 点击+1触发重渲染,value=1 simulateRender(2); // 再点击+1触发重渲染,value=2 // 1秒后会依次输出: // 当前渲染批次拿到的value:0 // 当前渲染批次拿到的value:1 // 当前渲染批次拿到的value:2
内容的提问来源于stack exchange,提问作者Rishabh Singh
相关产品推荐
相关产品推荐

