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

React setState时机问题:记忆游戏中状态与变量的选择困惑

React记忆卡牌游戏:状态管理选择与代码评审

一、状态选择的核心逻辑

你遇到的state异步更新问题,本质是没掌握React状态更新的正确姿势,而非state不如普通变量。明确两者的适用场景:

  • 必须用state的场景:数据需要驱动组件渲染(比如分数变化、已点击卡牌列表更新同步UI)、数据需要在组件重新渲染后保留。React的state更新是异步批量处理的,要获取最新状态必须用函数式更新(如setState(prevState => newState)),而非直接依赖当前state变量。
  • 可以用普通变量的场景:仅在当前渲染周期内临时使用的变量(比如洗牌时的临时计数器、循环变量),不需要触发渲染,也不需要跨渲染周期保留。

你现在用普通变量clicked的做法完全错误——一旦App组件因任何原因重新渲染(比如Set组件更新触发父组件重绘),clicked会被重置为空数组,游戏数据直接丢失。

二、代码评审与优化建议

1. App组件问题与修复

核心问题:用普通变量存储游戏状态导致数据丢失;未传递分数状态给Score组件;state更新方式错误。
优化后代码:

import './App.css';
import Set from './components/set';
import Score from './components/score';
import { useState } from 'react';

function App() {
  const [clicked, setClicked] = useState([]);
  const [currentScore, setCurrentScore] = useState(0);
  const [bestScore, setBestScore] = useState(0);

  function checkClicked(name) {
    // 用函数式更新获取最新的clicked状态
    setClicked(prevClicked => {
      if (prevClicked.includes(name)) {
        // 重复点击,重置游戏
        setBestScore(prev => Math.max(prev, currentScore));
        setCurrentScore(0);
        alert('重复点击,游戏重置!');
        return [];
      } else {
        const newClicked = [...prevClicked, name];
        if (newClicked.length === 5) {
          // 全部卡牌点击完成
          setBestScore(5);
          setCurrentScore(0);
          alert('全部卡牌点击完成,游戏重置!');
          return [];
        } else {
          setCurrentScore(prev => prev + 1);
          return newClicked;
        }
      }
    });
  }

  return (
    <>
      <Score current={currentScore} best={bestScore} />
      <Set action={checkClicked} />
    </>
  );
}

export default App;

2. Set组件问题与修复

核心问题:直接在useState中存储React元素,灵活性差;洗牌算法效率低。
优化后代码:

import { useState } from "react";
import Card from "./card";

export default function Set(props){
  // 存储卡牌数据而非React元素,更灵活
  const [cards, setCards] = useState([
    {name: "John", pic: "1", id: "card-1"},
    {name: "Al", pic: "2", id: "card-2"},
    {name: "Paul", pic: "3", id: "card-3"},
    {name: "James", pic: "4", id: "card-4"},
    {name: "Lou", pic: "5", id: "card-5"}
  ]);

  function handleAction(name){
    props.action(name);
    shuffle();
  }

  // 优化为Fisher-Yates洗牌法,高效简洁
  function shuffle(){
    const newCards = [...cards];
    for (let i = newCards.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [newCards[i], newCards[j]] = [newCards[j], newCards[i]];
    }
    setCards(newCards);
  }

  return(
    <>
      <button onClick={shuffle}>Shuffle</button>
      <div className="card-set">
        {cards.map(card => (
          <Card 
            key={card.id} 
            onClick={() => handleAction(card.name)} 
            name={card.name} 
            pic={card.pic}
          />
        ))}
      </div>
    </>
  );
}

3. Card组件简化

直接内联点击事件,减少不必要的函数定义:

export default function Card(props){
  return(
    <div onClick={() => props.onClick(props.name)}>
      <p>{props.name}</p>
      <p>{props.pic}</p>
    </div>
  );
}

4. 其他潜在问题

  • Score组件需要接收current和best props来显示分数,需补充该组件的实现逻辑。
  • 卡牌的key应使用固定唯一标识(如预设的id),而非动态生成的uniqid,避免组件不必要的重新渲染。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 22:02:43