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

为何useState在DOM中更新正常,但控制台始终输出初始值?

useState定时器中变量值不更新的问题

我用useState实现了一个带计时器的函数组件,DOM能实时正确显示时间,但停止计时器时把时间存入数组,却始终存初始值0。调试发现定时器循环里console.log(time)一直输出0,不是实际时间。

原始代码:

export default function Timer() {
    const [time, setTime] = useState(0);
    const interval = useRef(null);

    clearInterval(interval.current);
    let startTime = Date.now() - time;
    interval.current = setInterval(() => {
        setTime(Date.now() - startTime);            
        console.log(time); // <-- 为什么一直显示0?
    }, 10);

    return (
        <div>
            <span>{"0" + Math.floor((time / 60000) % 1000)).slice(-2)}:</span>
            <span>{"0" + Math.floor((time / 1000) % 1000)).slice(-2)}.</span>
            <span>{"00" + (time % 1000)).slice(-3, -1)}</span>
        </div>
    );
}

我的疑问:为什么time在DOM里能实时显示正确值,控制台却一直输出0?原以为是useState更新不及时,但始终输出0的情况无法理解。

编辑:
我通过声明单独变量curTime解决了问题,修改后的代码如下:

export default function Timer() {
    const [time, setTime] = useState(0);
    const interval = useRef(null);
    let curTime = 0

    clearInterval(interval.current);
    let startTime = Date.now() - time;
    interval.current = setInterval(() => {
        curTime = (Date.now() - startTime);     
        setTime(curTime)    
        console.log(curTime); // <-- 现在显示正确时间了
    }, 10);

    return (
        <div>
            <span>{"0" + Math.floor((time / 60000) % 1000)).slice(-2)}:</span>
            <span>{"0" + Math.floor((time / 1000) % 1000)).slice(-2)}.</span>
            <span>{"00" + (time % 1000)).slice(-3, -1)}</span>
        </div>
    );
}

问题原因与解释

这是React函数组件里闭包特性导致的典型坑:

  • 函数组件每次渲染都会重新执行整个组件代码,生成全新的变量环境。useState返回的time是当前渲染周期的“快照”——每次渲染的time都是独立的变量。
  • 你在组件第一次渲染时创建了定时器,定时器的回调函数会“捕获”当时的time变量(也就是初始值0)。之后哪怕setTime触发组件重新渲染,旧的定时器回调还是持有第一次渲染时的time引用,不会自动更新到新的time值,所以console.log(time)永远输出0。
  • 而DOM能正确显示,是因为每次setTime都会触发组件重新渲染,新的渲染周期会拿到最新的time快照,用来渲染UI自然是正确的。

你用curTime解决问题的思路是对的:这个变量是定时器回调里每次实时计算出来的,不受闭包捕获旧变量的影响,直接把最新值传给setTime,打印它当然能得到正确结果。

再给两个更符合React习惯的优化方案:

  1. 用useRef存储实时值
    useRef的.current属性不会随渲染周期变化,能一直保存最新值:
export default function Timer() {
    const [time, setTime] = useState(0);
    const timeRef = useRef(0);
    const interval = useRef(null);

    clearInterval(interval.current);
    let startTime = Date.now() - time;
    interval.current = setInterval(() => {
        const newTime = Date.now() - startTime;
        timeRef.current = newTime;
        setTime(newTime);            
        console.log(timeRef.current); // 这里能拿到实时时间
    }, 10);

    return (
        <div>
            <span>{"0" + Math.floor((time / 60000) % 1000)).slice(-2)}:</span>
            <span>{"0" + Math.floor((time / 1000) % 1000)).slice(-2)}.</span>
            <span>{"00" + (time % 1000)).slice(-3, -1)}</span>
        </div>
    );
}
  1. 用setTime的函数式更新
    如果需要基于旧state计算新值,直接给setTime传函数,就能拿到最新的旧值:
setTime(prevTime => {
  const newTime = Date.now() - startTime;
  console.log(newTime); // 这里能拿到实时计算的时间
  return newTime;
});

内容的提问来源于stack exchange,提问作者Jack

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 16:30:58