React中调用setState后,为何1000ms后console.log仍输出旧值?
问题描述
以下是一段React代码:
const [counter, setCounter] = useState(0); const add = () => { setCounter((prev) => prev + 1); // old value console.log(counter); // also old value setTimeout(() => { console.log(counter); }, 1000); }; return ( <div> <div>{counter}</div> <button onClick={(e) => add()}>+</button> </div> );
调用add函数更新counter状态后,立即执行的console.log输出旧值这点我已理解,但为何1000ms后setTimeout回调中的console.log仍输出旧值?我正试图理解React相关机制,望得到解答。
解答
核心原因是React函数组件的闭包捕获特性,结合状态更新触发组件重新渲染的机制:
- 函数组件每次渲染时,会生成一套全新的变量(包括状态
counter、函数add),这些变量的作用域绑定在当前渲染周期内。 - 点击按钮调用的
add,是当前渲染周期的版本,它捕获的counter就是该周期的旧值(比如初始的0)。 setCounter会触发组件重新渲染,生成新的counter(比如1)和新的add函数,但已经执行的add里的setTimeout回调,依然绑定在旧渲染周期的闭包上,只能访问旧周期里的counter值,所以1秒后输出的还是旧值。
简单说,setTimeout的回调在创建时就“锁定”了当时所在渲染周期的counter,后续组件重新渲染产生的新counter和这个回调没有关联。
如果想在setTimeout里拿到更新后的counter,有两种常用方案:
- 用
useRef存储最新值:ref的current属性在组件生命周期中是可变的,不会被闭包捕获固定值const counterRef = useRef(0); const [counter, setCounter] = useState(0); const add = () => { const newCounter = counter + 1; setCounter(newCounter); counterRef.current = newCounter; setTimeout(() => { console.log(counterRef.current); // 输出更新后的值 }, 1000); }; - 用
useEffect监听counter变化,在状态更新后执行异步操作useEffect(() => { const timer = setTimeout(() => { console.log(counter); }, 1000); return () => clearTimeout(timer); }, [counter]); // counter变化时重新执行effect
内容的提问来源于stack exchange,提问作者Ran Cheng
相关产品推荐
相关产品推荐

