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和bestprops来显示分数,需补充该组件的实现逻辑。 - 卡牌的key应使用固定唯一标识(如预设的id),而非动态生成的uniqid,避免组件不必要的重新渲染。
内容的提问来源于stack exchange,提问作者wavesinaroom
相关产品推荐
相关产品推荐

