React useEffect定时器更新对象状态触发stale state问题求解
问题根因
你的问题本质就是闭包捕获陈旧state:
- 你把
useEffect的依赖设为空数组,所以effect只会在组件首次挂载时执行一次,里面引用的gameLoop永远是首次渲染时创建的版本 - 首次渲染时的
gameLoop闭包捕获的thing永远是初始值{x:0, y:0},所以不管定时器执行多少次,你打印的都是初始值 - 你用函数式更新
setThing(prev => ...)的时候,state本身其实是在正常更新的,只是你打印的是闭包里的旧值而已
修复方案
方案1:用useRef存储最新状态(改动最小)
用ref存储最新的state值,所有闭包都可以通过ref拿到最新值:
import { useEffect, useState, useRef } from "react" const Test = () => { const [thing, setThing] = useState({ x: 0, y: 0 }); // 新建ref同步最新state const thingRef = useRef(thing); // 每次state更新时同步到ref useEffect(() => { thingRef.current = thing; }, [thing]); useEffect(() => { const timer = setInterval(gameLoop, 1000); // 一定要加清理函数,防止组件卸载后定时器继续执行 return () => clearInterval(timer); }, []); const gameLoop = () => { // 从ref拿最新值打印 console.log(thingRef.current) // 函数式更新保证state计算正确 setThing(prevP => ({...prevP, x: prevP.x+1})); } return ( <div> </div> ) } export default Test;
方案2:把逻辑放到useEffect内,更新依赖并清理定时器
import { useEffect, useState } from "react" const Test = () => { const [thing, setThing] = useState({ x: 0, y: 0 }); useEffect(() => { const gameLoop = () => { console.log(thing); setThing(prevP => ({...prevP, x: prevP.x + 1})); } const timer = setTimeout(gameLoop, 1000); // 每次依赖变化时清理上一次的定时器 return () => clearTimeout(timer); // 把thing加入依赖,每次state更新effect重新执行,拿到最新的thing }, [thing]); return ( <div> </div> ) } export default Test;
这个方案的执行逻辑是每次thing更新后,都会生成新的定时器,1秒后执行下一次更新,完全符合每秒更新一次的要求,不会出现执行速度变快的问题——你之前出现速度变快是因为没有加清理函数,旧的定时器没有被销毁,多个定时器同时执行导致的。
内容的提问来源于stack exchange,提问作者bluewave41
相关产品推荐
相关产品推荐

