React中useEffect内setState更新后日志值始终为0的问题排查
问题原因与解决方案
这是React开发里非常常见的闭包陷阱问题,我来给你一步步拆解清楚:
为什么打印的currentSalary始终是0?
当你在useEffect中创建setInterval定时器时,回调函数会捕获它创建时所在作用域的currentSalary变量——也就是组件第一次渲染时的初始值0。
每次调用setCurrentSalary更新状态时,组件会重新渲染,生成一个全新的currentSalary变量(持有更新后的值),但定时器的回调函数仍然绑定在最初那个闭包上,引用的还是初始的0,所以你在定时器里打印的永远是初始值,哪怕页面显示已经正常更新了。
另外你的代码还有个隐藏问题:useEffect没有设置依赖数组,这会导致每次组件渲染都会创建一个新的定时器,最终多个定时器同时运行,虽然页面看起来正常,但会造成不必要的性能浪费。
修复方案
方案1:用useRef保存最新状态值
useRef的.current属性是可变的,而且不会触发组件重新渲染,我们可以用它同步保存最新的currentSalary值,这样定时器里就能拿到实时更新的状态:
import { useState, useEffect, useRef } from 'react'; function App() { const [date, setDate] = useState(); const [salaryPerHour] = useState(145); const [salaryPerSecond] = useState(salaryPerHour / 60 / 60); const initialSalary = 0; const [currentSalary, setCurrentSalary] = useState(initialSalary); // 创建ref保存最新的currentSalary const currentSalaryRef = useRef(currentSalary); // 当currentSalary更新时,同步更新ref的值 useEffect(() => { currentSalaryRef.current = currentSalary; }, [currentSalary]); useEffect(() => { console.log("Use effect at App.js"); if (!date) { const intervalId = setInterval(() => { updateDate(); // 确保你已经实现了这个更新日期的函数 setCurrentSalary(prevCurrentSalary => prevCurrentSalary + salaryPerSecond); // 打印ref中保存的最新值 console.log(currentSalaryRef.current); }, 1000); // 清理函数:组件卸载或依赖变化时清除定时器 return () => clearInterval(intervalId); } }, [date, salaryPerSecond]); // 添加依赖数组,避免重复创建定时器 // 示例updateDate函数实现 const updateDate = () => { setDate(new Date()); }; return ( <div> <p>当前工资:{currentSalary.toFixed(4)}</p> </div> ); }
方案2:单独用useEffect监听状态变化打印
如果你只是想在currentSalary更新时打印它的值,完全不需要在定时器里做,直接用另一个useEffect监听状态变化即可,代码更简洁:
// 监听currentSalary变化,每次更新时自动打印最新值 useEffect(() => { console.log(currentSalary); }, [currentSalary]);
额外提醒
- 永远不要忽略
useEffect的依赖数组,否则会导致不必要的重复执行,甚至内存泄漏。 - 当你需要在异步操作(比如定时器、网络请求)中访问最新的状态时,优先考虑使用函数式更新(比如
setCurrentSalary(prev => prev + ...))或者useRef,这两种方式都能避开闭包陷阱。
内容的提问来源于stack exchange,提问作者Bobby Rösehag
相关产品推荐
相关产品推荐

