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

React函数组件中setInterval状态不更新原因及不使用useRef的解决方法

问题原因解释

React函数组件的每一次渲染都对应一个独立的作用域,本次渲染内声明的所有state、函数都属于这个作用域的快照。你在点击按钮调用tick启动setInterval的时候,回调函数捕获的是本次渲染作用域内的foodLevel、healthLevel等状态值,这些值在定时器运行的全程都不会更新,所以打印出来永远是初始值10。
你用函数式更新setFoodLevel(prev => prev -1)能正常更新页面,是因为React会把更新函数推入队列,执行时自动传入最新的状态值,不需要你自己访问当前状态,所以页面渲染是正常的。

不使用useRef的实现方案

下面两种方案都不需要为每个状态单独创建ref,完全符合React的设计范式:

方案1:用useEffect管理定时器,依赖项注入需要用到的状态

每次状态更新后,清除旧的定时器,用最新的状态重新启动定时器,这样新的定时器回调就能捕获到最新的状态值:

const Cat = () => {
    const [name, setName] = useState(null);
    const [foodLevel, setFoodLevel] = useState(10);
    const [healthLevel, setHealthLevel] = useState(10);
    const [warning, setWarning] = useState('');
    const [isGameRunning, setIsGameRunning] = useState(false);
    const [value, setValue] = useState('');

    const onChangeHandler = (event) => {
        setValue(event.target.value);
    };

    const startClickHandler = () => {
        setName(value);
        setIsGameRunning(true);
    };

    // 用useEffect管理定时器生命周期
    useEffect(() => {
        if (!isGameRunning || healthLevel === 0) return;

        const intervalId = setInterval(() => {
            if (foodLevel > 0) {
                setFoodLevel(prev => prev - 1);
                console.log(foodLevel); // 可正常获取最新状态
            } else {
                setHealthLevel(prev => prev - 1);
                console.log(healthLevel); // 可正常获取最新状态
            }
        }, 1000);

        // 状态变化、组件卸载、游戏结束时自动清理定时器
        return () => clearInterval(intervalId);
    }, [isGameRunning, foodLevel, healthLevel]);

    return(...)
};

方案2:用setTimeout递归调度替代setInterval

不需要维护定时器id,每次调度的setTimeout回调都属于最新的渲染作用域,天然能拿到最新状态,相比固定间隔的setInterval,更适合这种依赖状态变化的定时任务:

const Cat = () => {
    const [name, setName] = useState(null);
    const [foodLevel, setFoodLevel] = useState(10);
    const [healthLevel, setHealthLevel] = useState(10);
    const [warning, setWarning] = useState('');
    const [isGameRunning, setIsGameRunning] = useState(false);
    const [value, setValue] = useState('');

    const onChangeHandler = (event) => {
        setValue(event.target.value);
    };

    const startClickHandler = () => {
        setName(value);
        setIsGameRunning(true);
    };

    useEffect(() => {
        if (!isGameRunning || healthLevel === 0) return;

        const timer = setTimeout(() => {
            if (foodLevel > 0) {
                setFoodLevel(prev => prev - 1);
                console.log(foodLevel);
            } else {
                setHealthLevel(prev => prev - 1);
                console.log(healthLevel);
            }
        }, 1000);

        return () => clearTimeout(timer);
    }, [isGameRunning, foodLevel, healthLevel]);

    return(...);
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 06:57:04