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

React首次渲染时如何获取数组中两个不重复随机对象(二十一点场景)

解决二十一点首次发两张不重复牌的问题

你当前代码里的核心问题在于首次发牌时,两次更新牌组的操作是异步的,导致第二次取牌还是用的初始牌组,而且还有索引取值错误的问题。下面是具体分析和优化方案:

原代码的问题点

  • 首次发牌的useEffect中,setDeck是异步更新状态,第二次取randomCard1时,deck还没更新成过滤后的数组,所以两张牌可能重复
  • chosenCard1 = chosenCardArray1[1]是错误的:过滤出的数组只有匹配index的卡牌(通常只有一张),取索引1会得到undefined
  • 依赖多个状态的useEffect容易引发状态不同步,增加调试复杂度

优化后的实现方案

我们可以把抽牌逻辑封装成独立函数,保证每次抽牌都基于最新的牌组,同时简化状态管理:

1. 封装通用抽牌函数

这个函数可以从当前牌组中抽取指定数量的不重复卡牌,返回新牌组和抽到的卡牌:

const drawCards = (currentDeck, count) => {
  const deckCopy = [...currentDeck];
  const drawnCards = [];
  for (let i = 0; i < count; i++) {
    if (deckCopy.length === 0) break; // 防止牌组为空时报错
    const randomIndex = Math.floor(Math.random() * deckCopy.length);
    drawnCards.push(deckCopy.splice(randomIndex, 1)[0]);
  }
  return { newDeck: deckCopy, drawnCards };
};

用splice直接从牌组副本中移除抽到的卡牌,既高效又能保证不重复。

2. 重构组件代码

去掉冗余状态,用封装好的函数处理发牌和重置逻辑:

import { useState, useEffect } from 'react'
import Card from '../components/Card';
import Total from '../components/Total';
import { deckArray } from '../utils/data'

export default function Home(){
    const [usersCards, setUsersCards] = useState([])
    const [deck, setDeck] = useState(deckArray)
    const [total, setTotal] = useState(0)
    const [isStarted, setIsStarted] = useState(false)

    // 抽牌函数
    const drawCards = (currentDeck, count) => {
      const deckCopy = [...currentDeck];
      const drawnCards = [];
      for (let i = 0; i < count; i++) {
        if (deckCopy.length === 0) break;
        const randomIndex = Math.floor(Math.random() * deckCopy.length);
        drawnCards.push(deckCopy.splice(randomIndex, 1)[0]);
      }
      return { newDeck: deckCopy, drawnCards };
    };

    // 点击PLAY后发两张初始牌
    useEffect(() => {
        if(isStarted){
            const { newDeck, drawnCards } = drawCards(deck, 2);
            setDeck(newDeck);
            setUsersCards(drawnCards);
        }
    }, [isStarted, deck])

    // HIT按钮点击逻辑:发一张牌
    const handleHit = () => {
        const { newDeck, drawnCards } = drawCards(deck, 1);
        setDeck(newDeck);
        setUsersCards(prev => [...prev, ...drawnCards]);
    };

    // 重置游戏
    const handleReset = () => {
        setUsersCards([]);
        setDeck(deckArray);
        setTotal(0);
        setIsStarted(false);
    };

    return (
        <>
            <Card usersCards={usersCards} />

            {!isStarted && <button onClick={() => setIsStarted(true)}>PLAY</button>}

            {isStarted && <>
            <Total usersCards={usersCards} total={total} setTotal={setTotal}/>
            <button onClick={handleHit}>HIT</button>
            <button>STAND</button>
            <button onClick={handleReset}>RESET</button>
            </>}
            
        </>
    )
}

优化的核心点

  • 移除了dealCards、isReset这两个冗余状态,直接用函数处理事件,减少状态依赖
  • 抽牌逻辑统一封装,避免重复代码,确保每次抽牌都基于最新的牌组状态
  • 首次发牌时直接将usersCards设置为抽到的两张牌,符合游戏初始逻辑,避免追加错误
  • 用splice操作牌组副本,比filter更高效,逻辑更直观

额外建议

  • 可以把抽牌、重置、计算总值这些逻辑封装成自定义Hook(比如useBlackjackDeck),让组件代码更简洁,逻辑更集中
  • 可以给drawCards函数加边界判断,比如牌组为空时提示玩家无法继续发牌

内容的提问来源于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 21:50:14