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
相关产品推荐
相关产品推荐

