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

React中useState与useEffect实现计时器的重复提交问题

问题分析与解决方案

核心问题:闭包陷阱导致的状态捕获异常

你的问题根源在于React闭包特性:

  • useEffect 首次执行时,decTimer 捕获的是组件初始渲染时的 sent 值(false),后续即使手动点击提交将 sent 设为 true,旧闭包里的 sent 依然是初始值,导致计时器到点仍会触发 sendAnswer。
  • 自动调用 sendAnswer 时发送默认值,也是因为旧闭包捕获的输入状态是初始值,而非用户更新后的最新值。

修复方案

1. 用 useRef 持久化定时器ID

替换原有的 let timerInterval,使用 useRef 存储定时器ID,避免组件重渲染时丢失定时器引用:

const timerIntervalRef = useRef(null);

2. 更新 useEffect 依赖与定时器逻辑

将 sent 加入依赖数组,确保 decTimer 能捕获到最新的 sent 状态;同时在 sent 变为 true 时立即清除定时器,停止倒计时:

useEffect(() => {
  // 如果已提交,直接清除定时器
  if (sent) {
    if (timerIntervalRef.current) {
      clearInterval(timerIntervalRef.current);
      timerIntervalRef.current = null;
    }
    return;
  }

  const decTimer = () => {
    setTimerTime((oldTimerTime) => {
      if (oldTimerTime === 1) {
        if (!sent) {
          sendAnswer();
        }
      }
      return oldTimerTime - 1;
    });
  };

  // 监听SSE事件启动定时器(注意:避免重复绑定事件,可考虑在组件挂载时绑定一次)
  const handleGameEvent = (event) => {
    // 重置计时器初始值
    setTimerTime(30);
    // 先清除旧定时器再启动新的
    if (timerIntervalRef.current) {
      clearInterval(timerIntervalRef.current);
    }
    timerIntervalRef.current = setInterval(decTimer, 1000);
  };

  sharedEventSource.addEventListener("game", handleGameEvent);

  // 组件卸载或依赖变化时清理
  return () => {
    sharedEventSource.removeEventListener("game", handleGameEvent);
    if (timerIntervalRef.current) {
      clearInterval(timerIntervalRef.current);
    }
  };
}, [sharedEventSource, sent]); // 加入sent作为依赖

3. 确保 sendAnswer 使用最新状态

如果你的输入内容是通过 useState 管理的(比如 const [answer, setAnswer] = useState('')),直接在 sendAnswer 中使用 answer 即可,因为组件每次渲染都会重新创建 sendAnswer 函数,它会捕获最新的 answer 状态:

const sendAnswer = async () => {
  // 直接使用最新的answer状态
  await fetch("......", {
    method: "POST",
    body: JSON.stringify({ answer }),
    // 其他配置...
  });
  setSent(true);
};

4. 优化:手动提交时立即清除定时器

在 sendAnswer 中主动清除定时器,避免倒计时继续执行:

const sendAnswer = async () => {
  // 清除定时器
  if (timerIntervalRef.current) {
    clearInterval(timerIntervalRef.current);
    timerIntervalRef.current = null;
  }

  await fetch("......", {/*...*/});
  setSent(true);
};

关键注意点

  • 始终在组件卸载或依赖变化时清理定时器和SSE事件监听,防止内存泄漏。
  • 避免重复绑定SSE事件,若 sharedEventSource 是全局实例,建议在组件挂载时仅绑定一次事件,而非每次依赖变化都绑定。

内容的提问来源于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 22:52:43