React黑杰克应用:根据用户选择修改A牌分值的问题
黑杰克手牌模拟应用:A牌分值设置与总分更新问题修复
问题描述
开发简易黑杰克手牌模拟应用时,需要让用户拿到A牌时选择其分值为1或11。目前代码能定位到A牌的索引,但无法更新A牌的value值,页面上的总分也无法同步更新。
问题分析
- 临时变量修改无法触发重渲染:
total是组件渲染时通过usersCards.reduce计算的临时变量,直接执行total += value不会触发React重渲染,页面自然不会更新。 - A牌定位逻辑不准确:使用
e.card === aceToSet.card查找索引时,若牌堆中有多张A,会匹配到错误的卡牌,应该用唯一标识(如index)定位。 - 无限重渲染循环:组件渲染时直接判断
if(isStarted)并调用deal(),而deal()中的状态更新会再次触发渲染,导致循环调用,干扰正常状态更新。 - 异步状态更新的即时访问问题:
setStarterDeck是异步操作,调用后立即打印starterDeck只能拿到旧值,无法看到更新后的状态。
修复后的完整代码
import { useState, useEffect, Fragment } from 'react' import Card from '../components/Card'; import Total from '../components/Total'; import Popup from '../components/Popup' import AceSetterModal from '../components/AceSetterModal'; import { shuffle } from '../hooks/shuffleCards' import { deckArray } from '../utils/data' export default function Home(){ const startHandSize = 2 const lowAce = 1 const highAce = 11 const goal = 21 const [starterDeck, setStarterDeck] = useState(shuffle(deckArray)) const [howManyDealt, setHowManyDealt] = useState(startHandSize) const [triggerPopup, setButtonPopup] = useState(false) const [checker, setChecker] = useState(startHandSize); const [isStarted, setIsStarted] = useState(false) const [aceToSet, setAceToSet] = useState(null) const deal = () => { setHowManyDealt(startHandSize) setStarterDeck(shuffle(deckArray)) setChecker(0) } // 用useEffect替代直接在渲染逻辑中调用deal,避免无限循环 useEffect(() => { if(isStarted){ deal() setIsStarted(false) // 执行后重置状态,防止重复触发 } }, [isStarted]) const hit = () => !bust && setHowManyDealt(prev => prev + 1) const usersCards = starterDeck.slice(-howManyDealt) // total每次渲染都从usersCards重新计算,确保和最新卡牌状态同步 const total = usersCards.reduce((a, e) => a + e.value, 0); const bust = total > 21; const handleAceSet = value => { setStarterDeck(prevDeck => { // 用唯一的index定位要修改的A牌 const cardIndex = prevDeck.findIndex(card => card.index === aceToSet.index) if(cardIndex === -1) return prevDeck; // 未找到则返回原数组 // 创建新数组,修改目标卡牌的value return [ ...prevDeck.slice(0, cardIndex), {...prevDeck[cardIndex], value}, ...prevDeck.slice(cardIndex + 1) ] }) setAceToSet(null) } return( <div> {aceToSet ? <AceSetterModal handleSetLow={() => handleAceSet(lowAce)} handleSetHigh={() => handleAceSet(highAce)} /> : <Fragment> <button onClick={deal}>DEAL</button> <button disabled={bust} onClick={hit}>HIT</button> <button disabled={bust}>STAND</button> <Total total={total} usersCards={usersCards}/> {usersCards.map(card => ( <Card key={card.index} handleAceSet={() => setAceToSet(card)} card={card} /> ))} </Fragment> } </div> ) }
关键修复点说明
- 移除渲染阶段的直接函数调用:用
useEffect监听isStarted状态,仅当状态变化时执行deal(),并执行后重置状态,避免无限循环。 - 正确计算总分:
total始终通过当前usersCards计算,确保每次状态更新后都能得到最新的总分,无需手动修改临时变量。 - 精准定位A牌:使用唯一的
index属性查找目标卡牌,避免多张A时的匹配错误。 - 遵守不可变更新原则:修改
starterDeck时,始终创建新的数组和卡牌对象,确保React能检测到状态变化并触发重渲染。
内容的提问来源于stack exchange,提问作者01nowicj
相关产品推荐
相关产品推荐

