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

