如何在useEffect中优雅使用最新的状态变量?
如何在useEffect中优雅使用最新的状态变量?
嘿,这个场景我太熟了!你碰到的就是React里经典的闭包陷阱——useEffect里的定时器函数捕获的是组件首次渲染时的count值(也就是1),所以10秒后打印的不是预期的10,而是初始的1。而且你说的没错,如果直接把count加到useEffect的依赖里,会导致每次count更新都重新创建定时器,那计数速度会直接起飞,完全是反效果。
给你分享两个优雅的解决思路,都能避开这些坑:
方法一:用useRef保存最新状态的引用
React的useRef就是专门用来解决这种“需要跨渲染周期访问最新值”的场景的,它的current属性是可变的,不会像状态变量那样被闭包“固定”住。我们只需要加一小段代码同步状态到ref就行:
function Counter() { const [count, setCount] = useState(1); const countRef = useRef(count); // 每次count更新时,同步到ref里 useEffect(() => { countRef.current = count; }, [count]); useEffect(() => { const timeout = setInterval(() => setCount(prev => prev + 1), 1000); setTimeout(() => console.log(`Count after 10': ${countRef.current}`), 10000); return () => clearTimeout(timeout); }, []); return <span>{count}</span>; }
原理很简单:单独的那个useEffect会在每次count变化时自动把最新值同步到countRef.current里,这样不管什么时候访问这个ref,拿到的都是当前最新的状态。而且外层的useEffect依然保持空依赖,不会重复创建定时器,完美解决了你的问题。
方法二:在状态更新时同步更新ref(可选)
如果你不想多写一个useEffect,也可以在更新状态的同时手动同步ref,不过这种方式适合状态更新逻辑比较集中的场景:
function Counter() { const [count, setCount] = useState(1); const countRef = useRef(count); const increment = () => { const newCount = count + 1; countRef.current = newCount; setCount(newCount); }; useEffect(() => { const timeout = setInterval(increment, 1000); setTimeout(() => console.log(`Count after 10': ${countRef.current}`), 10000); return () => clearTimeout(timeout); }, []); return <span>{count}</span>; }
不过要注意,如果后续有其他地方也会更新count,别忘了同步更新ref,不然就会出现ref和状态不一致的情况,所以方法一的通用性会更强一些。
你问能不能不用额外对象包裹就传引用?其实useRef就是React官方给的最优解了,它没有多余的样板代码,完全符合React的设计逻辑,比你自己手动封装对象要靠谱得多。
备注:内容来源于stack exchange,提问作者Herbert
相关产品推荐
相关产品推荐

