React开发2048游戏:点击按钮连续两次更新state仅单次生效
问题根源
连续调用两次generate()只生成一个2的本质原因是React的状态更新是异步批处理的:同一事件回调中执行两次generate()时,闭包内捕获到的Board始终是事件执行前的初始空棋盘,两次生成操作都基于同一份旧状态计算,最终后一次setBoard会覆盖前一次的更新结果。同时原generate()中随机到非空位置就递归调用自身的逻辑,在状态未同步更新的场景下还可能触发无限递归。
修复方案
核心修改点是使用React的函数式状态更新,确保每次修改棋盘时都能拿到上一次更新完成的最新状态,同时优化随机生成逻辑,先筛选所有空位再随机选择,避免不必要的递归:
import { useState, useEffect } from 'react'; const width = 4; const Game = () => { const [Board, setBoard] = useState([]); const createBoard = () => { let initialBoard = []; for (let i = 0; i < width * width; i++) { initialBoard.push(""); } return initialBoard; }; // 基于传入的最新棋盘生成一个新块,返回更新后的棋盘 const addRandomTile = (currentBoard) => { // 先找出所有空位置 const emptyPositions = []; currentBoard.forEach((val, idx) => { if (val === "") emptyPositions.push(idx); }); // 没有空位直接返回原棋盘 if (emptyPositions.length === 0) return currentBoard; // 从空位置里随机选一个填2 const randomIdx = Math.floor(Math.random() * emptyPositions.length); const targetPos = emptyPositions[randomIdx]; const newBoard = [...currentBoard]; newBoard[targetPos] = 2; return newBoard; }; const handleNewGame = () => { // 先初始化空棋盘,再连续加两个2,全程用函数式更新拿到最新状态 setBoard(createBoard()); setBoard(prevBoard => addRandomTile(prevBoard)); setBoard(prevBoard => addRandomTile(prevBoard)); } useEffect(() => { handleNewGame(); console.log(`Board Created!`); }, []); return ( <div className="game-container"> <button onClick={handleNewGame}>NewGame</button> <div className="game"> {Board.map((value, index) => ( <div className="tile" key={index}> {value} </div> ))} </div> </div> ); }; export default Game;
关键改动说明
- 把生成方块的逻辑抽成纯函数
addRandomTile,接收当前最新棋盘作为参数,不依赖组件闭包内的旧状态 - 所有状态更新都使用
setBoard(prev => 新值)的函数式写法,保证链式调用时每一步都能拿到上一步更新完成的最新状态 - 替换原有的随机+递归判断逻辑,先收集所有空位再随机选择,从根源上避免随机到非空位置的无效计算和递归风险
- 初始化和新游戏逻辑统一收敛到
handleNewGame,首次加载和点击按钮复用同一套逻辑,保证初始棋盘一定生成2个随机2块
内容的提问来源于stack exchange,提问作者Skydround
相关产品推荐
相关产品推荐

