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

React useState状态更新后console.log无法获取最新值问题求助

React useState闭包陷阱导致状态读取异常解析

这是典型的React闭包陷阱,你的decrementTime函数被useEffect里的setInterval捕获后,一直保留着组件第一次渲染时的remainingTime值(也就是初始的10)。

问题根源

  • useEffect的依赖数组仅包含remainingSeconds,因此只有当remainingSeconds变化时,才会重新执行useEffect并创建新定时器。
  • 第一次渲染时,decrementTime函数里的remainingTime被固定为初始值10,这个函数被定时器回调引用后,就被闭包“锁死”了。后续组件重新渲染时生成的新decrementTime函数,根本不会被旧定时器调用——因为定时器还是第一次创建的那个,它只认最初的decrementTime。

所以哪怕页面渲染的remainingTime已经正常递减,decrementTime里读取的始终是闭包捕获的旧值,导致console.log一直显示10,if(remainingTime>0)也永远为true。


解决方案

方案1:将判断逻辑移到函数式更新回调中

既然已经在使用函数式更新setRemainingTime((currentTime) => currentTime - 1),可以直接把判断逻辑嵌入这个回调,完全依赖最新的当前状态:

function decrementTime(): void {
  setRemainingTime((currentTime) => {
    const newTime = currentTime > 0 ? currentTime - 1 : currentTime;
    console.log("function gets called, state is : " + newTime);
    return newTime;
  });
}

这样每次调用setRemainingTime时,都会拿到最新的状态值,判断和更新逻辑都基于当前最新值,console.log也能输出正确数值。

方案2:用useCallback包裹函数并更新依赖

把decrementTime用useCallback包裹,让它依赖remainingTime,同时将decrementTime加入useEffect的依赖数组,确保状态变化时定时器能调用最新的函数:

import React, { useEffect, useState, useCallback } from "react";
import { FC } from "react";

interface ITimerProps {
  remainingSeconds: number;
}

const Timer: FC<ITimerProps> = ({ remainingSeconds = 10 }) => {
  const [remainingTime, setRemainingTime] = useState(remainingSeconds);

  const decrementTime = useCallback(() => {
    if (remainingTime > 0) {
      setRemainingTime((currentTime) => currentTime - 1);
    }
    console.log("function gets called, state is : " + remainingTime);
  }, [remainingTime]);

  useEffect(() => {
    const timer = setInterval(() => {
      decrementTime();
    }, 1000);

    return () => {
      clearInterval(timer);
    };
  }, [remainingSeconds, decrementTime]);

  return <div>{remainingTime}</div>;
};

export default Timer;

注意:这种方式每次状态变化都会销毁旧定时器、创建新定时器,虽然能解决问题,但对于倒计时场景来说,微小的性能影响完全可以忽略。

方案3:用useRef保存最新状态

使用useRef存储最新的remainingTime,因为ref的current属性是可变的,不会被闭包捕获旧值。每次状态更新时同步更新ref,在decrementTime里直接读取ref的current:

import React, { useEffect, useState, useRef } from "react";
import { FC } from "react";

interface ITimerProps {
  remainingSeconds: number;
}

const Timer: FC<ITimerProps> = ({ remainingSeconds = 10 }) => {
  const [remainingTime, setRemainingTime] = useState(remainingSeconds);
  const remainingTimeRef = useRef(remainingSeconds);

  // 同步状态到ref
  useEffect(() => {
    remainingTimeRef.current = remainingTime;
  }, [remainingTime]);

  useEffect(() => {
    const timer = setInterval(() => {
      decrementTime();
    }, 1000);

    return () => {
      clearInterval(timer);
    };
  }, [remainingSeconds]);

  function decrementTime(): void {
    if (remainingTimeRef.current > 0) {
      setRemainingTime((currentTime) => currentTime - 1);
    }
    console.log("function gets called, state is : " + remainingTimeRef.current);
  };

  return <div>{remainingTime}</div>;
};

export default Timer;

这种方式不需要频繁创建销毁定时器,适合对性能有更精细要求的场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 12:27:23