You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 17:09:57