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

React黑杰克应用:根据用户选择修改A牌分值的问题

黑杰克手牌模拟应用:A牌分值设置与总分更新问题修复

问题描述

开发简易黑杰克手牌模拟应用时,需要让用户拿到A牌时选择其分值为1或11。目前代码能定位到A牌的索引,但无法更新A牌的value值,页面上的总分也无法同步更新。

问题分析

  1. 临时变量修改无法触发重渲染:total是组件渲染时通过usersCards.reduce计算的临时变量,直接执行total += value不会触发React重渲染,页面自然不会更新。
  2. A牌定位逻辑不准确:使用e.card === aceToSet.card查找索引时,若牌堆中有多张A,会匹配到错误的卡牌,应该用唯一标识(如index)定位。
  3. 无限重渲染循环:组件渲染时直接判断if(isStarted)并调用deal(),而deal()中的状态更新会再次触发渲染,导致循环调用,干扰正常状态更新。
  4. 异步状态更新的即时访问问题: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 12:21:03