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

useEffect调用drawCard无法读取更新后的计数器状态,按钮调用正常

问题描述

我有一个名为drawCard的函数,它从API获取卡牌,然后根据卡牌修改计数器的状态。我在useEffect中调用它4次。计数器在HTML中显示的值是正确的,但当我打印计数器的值时,它始终返回初始值0。不过,当我通过按钮调用该函数时,一切都能正常工作。

为什么在useEffect中运行时无法获取更新后的值?我需要用它来判断A牌应该赋值为11还是1。


相关代码片段

TheGame.js(部分)

const [playerCounter, setPlayerCounter] = useState(0);
const [secondPlayerCounter, setSecondPlayerCounter] = useState(0);
const [croupierCounter, setCroupierCounter] = useState(0);
const [temporaryCount, setTemporaryCount] = useState(0);

useEffect(() => {
    const dealCards = async () => {
        await drawCard(setPlayerHand, playerCounter, setPlayerCounter, deck);
        console.log(playerCounter);
        await wait();
        await drawCard(setPlayerHand, playerCounter, setPlayerCounter, deck);
        console.log(playerCounter);
        await wait();
        await drawCard(setCroupierHand, temporaryCount, setTemporaryCount, deck, temporaryCount);
        await wait();
        await drawCard(setCroupierHand, croupierCounter, setCroupierCounter, deck, temporaryCount);
        console.log(playerCounter);
        console.log(croupierCounter);
    }
    if(isStarted) {
         dealCards();
    }
}, [isStarted]);

const onHitHandler = () => {                 // works fine 
        drawCard(setPlayerHand, playerCounter, setPlayerCounter, deck)
};

DrawCard.js

import {fetchCard} from '../components/Game/Fetch.js';

export const drawCard = async (handSetter, count, countSetter, deck, temporaryCount = 0) => {
    let card = await fetchCard(deck, 1);
    handSetter((oldHand) => [...oldHand, card])
    console.log(count);
    if (card[0].value === 'ACE') {
        if(count + temporaryCount + 11 <= 21) {
            countSetter(prevCount => prevCount + 11);
        }
        else {
            countSetter(prevCount => prevCount + 1);
        }
    }
    else if (card[0].value === 'JACK' || card[0].value === 'QUEEN' || card[0].value === 'KING') {
        countSetter(prevCount => prevCount + 10);
    }
    else {
        countSetter(prevCount => prevCount + Number(card[0].value));
    };
}

问题原因与解决方案

原因

React状态更新是异步的,且useEffect中的dealCards函数形成了闭包:当isStarted触发useEffect执行时,dealCards捕获的是此时的playerCounter值(初始0)。后续调用setPlayerCounter更新状态后,组件会重新渲染,但dealCards是在之前的渲染周期中创建的,它内部的playerCounter变量不会同步更新,所以打印出来的始终是初始值,判断A牌时用的也是旧状态。

而按钮点击的onHitHandler每次触发时,都是在当前最新的渲染周期中执行,捕获的是最新的playerCounter值,所以能正常工作。

解决方案

核心思路是不要依赖闭包中的旧状态值,而是通过countSetter的函数式更新来获取最新状态:

1. 修改drawCard函数

移除count参数,直接在countSetter的回调里用prevCount获取最新状态,以此判断A牌的赋值:

import {fetchCard} from '../components/Game/Fetch.js';

export const drawCard = async (handSetter, countSetter, deck, temporaryCount = 0) => {
    let card = await fetchCard(deck, 1);
    handSetter((oldHand) => [...oldHand, card])
    
    if (card[0].value === 'ACE') {
        countSetter(prevCount => {
            // 用最新的prevCount计算,避免依赖闭包中的旧值
            const totalIfAce11 = prevCount + temporaryCount + 11;
            return totalIfAce11 <= 21 ? prevCount + 11 : prevCount + 1;
        });
    }
    else if (card[0].value === 'JACK' || card[0].value === 'QUEEN' || card[0].value === 'KING') {
        countSetter(prevCount => prevCount + 10);
    }
    else {
        countSetter(prevCount => prevCount + Number(card[0].value));
    };
}

2. 修改调用逻辑

调用drawCard时不再传入playerCounter等状态值:

useEffect(() => {
    const dealCards = async () => {
        await drawCard(setPlayerHand, setPlayerCounter, deck);
        await wait();
        await drawCard(setPlayerHand, setPlayerCounter, deck);
        await wait();
        await drawCard(setCroupierHand, setTemporaryCount, deck, temporaryCount);
        await wait();
        await drawCard(setCroupierHand, setCroupierCounter, deck, temporaryCount);
        // 如果需要打印最新状态,可通过useRef保存,或单独用useEffect监听状态变化
    }
    if(isStarted) {
         dealCards();
    }
}, [isStarted]);

const onHitHandler = () => {
    drawCard(setPlayerHand, setPlayerCounter, deck)
};

补充:如何打印最新状态

如果需要在dealCards中打印最新的计数器值,可以用useRef保存状态的最新值:

const playerCounterRef = useRef(0);

// 监听状态变化,同步更新ref
useEffect(() => {
    playerCounterRef.current = playerCounter;
}, [playerCounter]);

// 在dealCards中打印
console.log(playerCounterRef.current);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 10:54:20