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

React中定时器回调内使用state变量的异常问题排查

问题解决:定时器触发时无法获取最新State值

问题原因

你碰到的是React经典的闭包陷阱:

  • 组件初始化时,useEffect创建了定时器,此时定时器里的updateTimer函数捕获了当时的sendAnswer。
  • sendAnswer作为组件内的函数,每次组件重新渲染(比如输入框改变answer时)都会重新创建,新的sendAnswer能拿到最新的answer和timerTime,但定时器里还是用的初始化时的旧sendAnswer,自然只能拿到state的初始值。

解决方案

给你两种简单有效的解决方式:

方式一:用useRef保存最新的State和sendAnswer

通过ref存储最新的state和函数,ref的current属性是可变的,不会被闭包锁定旧值:

import { useState, useEffect, useRef } from 'react';

function GameRound() {
    const [answer, setAnswer] = useState("");
    const [timerTime, setTimerTime] = useState(30);
    const canSendRef = useRef(true);
    // 保存最新的answer和timerTime
    const stateRef = useRef({
        answer: "",
        timerTime: 30
    });

    // 每次state更新时同步到ref
    useEffect(() => {
        stateRef.current = {
            answer,
            timerTime
        };
    }, [answer, timerTime]);

    const sendAnswer = async (autoSent) => {
        // 从ref里拿最新的state值
        const { answer, timerTime } = stateRef.current;
        fetch("...endpoint...", {
            // ...其他请求配置
            body: JSON.stringify({
                "answer": answer,
                "ready": !autoSent,
                "timeRemaining": timerTime
            })
        });
        // ...其他逻辑
    };

    useEffect(() => {
        const updateTimer = () => {
            setTimerTime((oldTimerTime) => {
                if(oldTimerTime === 1) {
                    if(canSendRef.current)
                        sendAnswer(true)
                }
                return oldTimerTime - 1;
            });
        };

        const timerInterval = setInterval(updateTimer, 1000);
        // 清理定时器,避免内存泄漏
        return () => clearInterval(timerInterval);
    }, [sharedEventSource]);

    return (
        <>
            {/*...其他页面元素*/}
            <input  
                type="text"
                value={answer}
                onInput={e => setAnswer(e.target.value)}
            />
            <div onClick={() => sendAnswer(false)}>提交答案</div>
            {/*...其他页面元素*/}
        </>
    );
}

方式二:修正useEffect的依赖数组,同时清理定时器

把sendAnswer加入useEffect的依赖数组,用useCallback缓存函数,避免不必要的重渲染:

import { useState, useEffect, useCallback } from 'react';

function GameRound() {
    const [answer, setAnswer] = useState("");
    const [timerTime, setTimerTime] = useState(30);
    const canSendRef = useRef(true);

    // 用useCallback缓存sendAnswer,依赖变化时才更新函数
    const sendAnswer = useCallback(async (autoSent) => {
        fetch("...endpoint...", {
            // ...其他请求配置
            body: JSON.stringify({
                "answer": answer,
                "ready": !autoSent,
                "timeRemaining": timerTime
            })
        });
        // ...其他逻辑
    }, [answer, timerTime]);

    useEffect(() => {
        const updateTimer = () => {
            setTimerTime((oldTimerTime) => {
                if(oldTimerTime === 1) {
                    if(canSendRef.current)
                        sendAnswer(true)
                }
                return oldTimerTime - 1;
            });
        };

        const timerInterval = setInterval(updateTimer, 1000);
        // 组件卸载或依赖变化时清理旧定时器
        return () => clearInterval(timerInterval);
    }, [sharedEventSource, sendAnswer]); // 加入sendAnswer作为依赖

    return (
        <>
            {/*...其他页面元素*/}
            <input  
                type="text"
                value={answer}
                onInput={e => setAnswer(e.target.value)}
            />
            <div onClick={() => sendAnswer(false)}>提交答案</div>
            {/*...其他页面元素*/}
        </>
    );
}

注意点

  • 两种方式都要在useEffect的返回函数里清理定时器,防止组件卸载后定时器持续运行导致内存泄漏。
  • 方式二中的useCallback是为了避免answer或timerTime每次变化时,sendAnswer都重新创建,导致useEffect频繁触发(这是合理的触发,因为函数本身需要更新以获取最新state)。

内容的提问来源于stack exchange,提问作者Crih.exe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 23:47:48