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

React卡片匹配游戏:点击卡片无法为choiceTwo赋值求助

React卡片匹配游戏选择状态赋值异常问题

我正在开发一款基于React的卡片匹配游戏,卡片通过data-keyMatch绑定数值。需求是:点击首张卡片将keyMatch值存入choiceOne状态;点击第二张卡片时,若choiceOne已有值则存入choiceTwo状态。

卡片元素导入自MatchingCard组件,点击事件在父组件中通过handleChoice函数绑定。我尝试将choiceOne、choiceTwo默认状态设为null或0,但点击卡片时始终仅能重复赋值choiceOne,choiceTwo从未被赋值。不过测试函数check的逻辑正常,相关代码如下:


父组件核心代码

// -----------------Matching Card Game Functionality --------------------
// --------------------------------------------------------------------------

// States
const [doubledDeck, setDoubledDeck] = useState([]);
const [shuffledDeck, setShuffledDeck] = useState([]);
const [deckReady, setDeckReady] = useState([]);
const [matchingGameActive, setMatchingGameActive] = useState(false);

const [turns, setTurns] = useState(0);
const [choiceOne, setChoiceOne] = useState(0);
const [choiceTwo, setChoiceTwo] = useState(0);

// 测试用函数,逻辑正常
const random = Math.floor(Math.random() * 10);
const check = () => {
  choiceOne != 0 ? setChoiceTwo(random) : setChoiceOne(random);

  if (turns === 0) {
    setChoiceOne(random);
    setTurns((prevTurn) => prevTurn + 1);
  }

  if (turns === 1) {
    setChoiceTwo(random);
    setTurns(0);
  }
};

// 生成双倍卡组
const doubleTheDeck = (deck: any) => {
  const FlashcardsEnglish = deck.map((card: { props: { children: { props: any }[] } }) => {
    return card.props.children[0].props;
  });
  const FlashcardsJapanese = deck.map((card: { props: { children: { props: any }[] } }) => {
    return card.props.children[1].props;
  });

  const joinedDeck = FlashcardsEnglish.concat(FlashcardsJapanese);

  setDoubledDeck(joinedDeck);
};

// 洗牌函数
const shuffle = (deck: any[]) => {
  const shuffledCards = deck
    .sort(() => Math.random() - 0.5)
    .map((card) => ({
      ...card,
    }));

  setShuffledDeck(shuffledCards);
};

// 卡片点击处理函数
const handleChoice = (e: { target: { dataset: { keyMatch: any } } }) => {
  const parsed = parseInt(e.target.dataset.keyMatch);
  // 尝试1:用null作为默认状态
  // choiceOne ? setChoiceTwo(parsed) : setChoiceOne(parsed);

  // 尝试2:用0作为默认状态
  if (turns === 0) {
    setChoiceOne(parsed);
    setTurns((prevTurn) => prevTurn + 1);
  }

  if (turns === 1) {
    setChoiceTwo(parsed);
    setTurns(0);
  }
};
console.log(choiceOne, choiceTwo);

// 生成卡组JSX
const finalDeck = shuffledDeck.map((card: { matchId: any; word: any }) => {
  const { matchId, word } = card;
  return (
    <MatchingCards key={matchId + word[0]} matchId={matchId} word={word} handleChoice={handleChoice}></MatchingCards>
  );
});

// 开始游戏处理
const handleMatchingGameClick = () => {
  doubleTheDeck(cardsForMatchingGame);
  shuffle(doubledDeck);
  setDeckReady(finalDeck);
  setMatchingGameActive((prevState) => !prevState);
};

// useEffect(() => {}, []);

return (
  <div>
    <div>
      <Header pageHeader={className} />
      <div className="bg-white">choice 1: {choiceOne}</div>
      <div className="bg-white">choice 2: {choiceTwo}</div>
      <button onClick={check}>check</button>
    </div>
    <div className="flex items-center  justify-between bg-slate-200 dark:bg-bd-1 p-4 ">
      <HomeButton />
      <MatchingGameButton
        content={matchingGameActive ? "Back to Regular Deck" : "Go to Matching Game"}
        onClick={handleMatchingGameClick}
      />
      <ToggleButton />
    </div>

    <div
      className="
      dark:bg-bd-1
      p-10
    bg-slate-200 gap-5 flex flex-col items-center justify-center
    sm:items-center sm:justify-center
    sm:grid
    sm:grid-cols-2
    md:grid 
    md:grid-cols-3 
    lg:grid-cols-4
     "
    >
      {matchingGameActive ? deckReady : cards};
    </div>
  </div>
);

MatchingCards组件返回内容

return (
  <div>
    <div>
      <Header pageHeader={className} />
      <div className="bg-white">choice 1: {choiceOne}</div>
      <div className="bg-white">choice 2: {choiceTwo}</div>
      <button onClick={check}>check</button>
    </div>
    <div className="flex items-center  justify-between bg-slate-200 dark:bg-bd-1 p-4 ">
      <HomeButton />
      <MatchingGameButton
        content={matchingGameActive ? "Back to Regular Deck" : "Go to Matching Game"}
        onClick={handleMatchingGameClick}
      />
      <ToggleButton />
    </div>

    <div
      className="
      dark:bg-bd-1
      p-10
    bg-slate-200 gap-5 flex flex-col items-center justify-center
    sm:items-center sm:justify-center
    sm:grid
    sm:grid-cols-2
    md:grid 
    md:grid-cols-3 
    lg:grid-cols-4
     "
    >
      {matchingGameActive ? deckReady : cards};
    </div>
  </div>
);

问题原因及修复方案

1. React状态更新异步性导致turns判断失效

在handleChoice中直接使用turns值判断时,由于React状态更新是异步的,调用setTurns后当前函数上下文的turns不会立即更新,第二次点击时turns仍为0,因此始终执行setChoiceOne。

修复代码:

const handleChoice = (e: React.MouseEvent<HTMLDivElement>) => {
  const parsed = parseInt(e.currentTarget.dataset.keyMatch!);
  setTurns(prevTurns => {
    if (prevTurns === 0) {
      setChoiceOne(parsed);
      return 1;
    } else {
      setChoiceTwo(parsed);
      return 0;
    }
  });
};

2. MatchingCards组件逻辑错误

你提供的MatchingCards组件完全复制了父组件的渲染内容,导致状态和事件逻辑混乱。正确的组件应仅负责渲染单张卡片,并将data-keyMatch绑定到自身元素上。

修正后的MatchingCards组件:

type MatchingCardsProps = {
  matchId: number;
  word: string[];
  handleChoice: (e: React.MouseEvent<HTMLDivElement>) => void;
};

const MatchingCards = ({ matchId, word, handleChoice }: MatchingCardsProps) => {
  return (
    <div 
      data-keyMatch={matchId} 
      onClick={handleChoice}
      className="p-4 bg-white rounded shadow"
    >
      <p>{word[0]}</p>
    </div>
  );
};

export default MatchingCards;

3. 卡组初始化的异步问题

handleMatchingGameClick中调用doubleTheDeck后立即执行shuffle(doubledDeck),但doubledDeck更新是异步的,此时传入的仍是旧数组。需用useEffect监听doubledDeck变化来执行洗牌:

useEffect(() => {
  if (doubledDeck.length > 0) {
    shuffle(doubledDeck);
  }
}, [doubledDeck]);

const handleMatchingGameClick = () => {
  doubleTheDeck(cardsForMatchingGame);
  setMatchingGameActive(prev => !prev);
};

// 渲染时直接生成卡组,无需存入state
// 替换return中的卡组渲染部分:
{matchingGameActive ? shuffledDeck.map(card => (
  <MatchingCards 
    key={card.matchId + card.word[0]} 
    matchId={card.matchId} 
    word={card.word} 
    handleChoice={handleChoice}
  />
)) : cards}

内容的提问来源于stack exchange,提问作者Boon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 20:10:50