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

JavaScript中setInterval异步函数内无法读取最新React state值如何解决

问题根因

这是React函数组件典型的闭包陷阱问题:

  1. React函数组件每次渲染对应独立的作用域,点击按钮触发onStandHandler时,该函数作用域内捕获的croupierCount是当前渲染对应的初始值0
  2. 后续调用setCroupierCount更新状态只会触发组件重新渲染、生成新的作用域,已启动的setInterval回调始终持有旧渲染作用域的croupierCount引用,所以永远读到初始值0
  3. 页面可正常展示最新值是因为视图绑定的是新渲染作用域的最新状态,和定时器回调持有的旧值互不影响

解决方案

推荐使用useRef存储可变的状态副本和定时器ID解决该问题,useRef的current属性是可变的,且在组件全生命周期内共享引用,不会被闭包捕获固定值,修正后的代码如下:

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

const TheGame = () => {
  const [croupierCount, setCroupierCount] = useState(0);
  const [croupierHand, setCroupierHand] = useState([]);
  // 存储croupierCount的最新值
  const croupierCountRef = useRef(0);
  // 存储定时器ID,方便全局清理
  const intervalRef = useRef(null);

  // 同步更新ref的最新值,同时判断停止条件
  useEffect(() => {
    croupierCountRef.current = croupierCount;
    if (croupierCount >= 17 && intervalRef.current) {
      clearInterval(intervalRef.current);
      intervalRef.current = null;
    }
  }, [croupierCount]);

  const onStandHandler = () => {
    // 先清掉之前可能存在的定时器,避免重复启动
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
    }

    const croupierInterval = async () => {
      let card = await fetchCard(deck);
      // 不要直接修改state,用函数式更新生成新数组
      setCroupierHand(prev => [...prev, card]);
      
      let addCount = 0;
      if (card[0].value === 'ACE') {
        addCount = 11;
      } else if (['JACK', 'QUEEN', 'KING'].includes(card[0].value)) {
        addCount = 10;
      } else {
        addCount = Number(card[0].value);
      }
      setCroupierCount(prev => prev + addCount);
      
      // 读取ref获取最新值
      console.log(croupierCountRef.current);
    }

    intervalRef.current = setInterval(croupierInterval, 1000);
  };

  // 组件卸载时清理定时器,避免内存泄漏
  useEffect(() => {
    return () => {
      if (intervalRef.current) {
        clearInterval(intervalRef.current);
      }
    }
  }, []);

  // 剩余组件渲染逻辑
};

其他注意事项

  • 原代码中croupierHand.push(card)属于直接修改引用类型状态的错误写法,必须通过setCroupierHand生成新数组更新状态,否则手牌视图不会正常刷新
  • 启动定时器前先清理已存在的定时器,可避免用户多次点击按钮导致多个定时器同时运行的异常

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 01:54:03