React中如何实现内容仅渲染一次?战舰游戏船只生成无限循环问题
问题根因分析
- 无限循环来自状态更新的逻辑冲突:你把船组重试逻辑放在了依赖
playerShipsArray的useEffect中,每次调用generateShips更新该状态都会重新触发这个useEffect,加上React的setState是异步更新,while循环读取的始终是当前闭包中的旧状态值,永远拿不到更新后的船组数据,自然会进入死循环。 generateShips函数逻辑冗余错误:内部while循环判断数组长度小于5,但嵌套的for循环一次就会往数组推入5条船数据,while循环实际没有任何作用。- 直接修改state原值:
placeShipsOnBoard直接修改playerBoard这个state对象,不符合React不可变状态的更新规则,会导致组件不会触发对应重渲染,页面棋盘不会同步更新。
修正方案
将船组生成、碰撞校验的逻辑合并到同一个函数内,不需要通过状态变化触发重试,在单次函数执行中完成所有校验,拿到合法船组后再一次性更新状态,同时修正棋盘更新逻辑,修正后代码如下:
const GameControl = () => { const [playerBoard, setPlayerBoard] = useState(() => GameBoard('player')); const [playerShipsArray, setPlayerShipsArray] = useState([]); // 初始化游戏仅执行一次 useEffect(() => { // 生成无碰撞的合法船组 const validShips = generateValidShips() setPlayerShipsArray(validShips) // 不可变方式更新棋盘,先创建新的棋盘实例再更新状态 const newBoard = GameBoard('player') placeShipsOnBoard(validShips, newBoard) setPlayerBoard(newBoard) }, []) // 合并生成+校验逻辑,直接返回合法船组 function generateValidShips() { let shipsArr = []; // 循环重试直到生成无碰撞的船组 while(true) { shipsArr = [] // 生成5艘不同长度的船 for(let i = 1; i < 6; i++){ let startCoord = Math.floor(Math.random() * 100); const ship = Ship(i, i, isVertical(), startCoord) shipsArr.push(ship) } // 校验无碰撞直接返回结果 if(checkTakenCells(shipsArr)) { return shipsArr } } } function checkTakenCells (array){ let takenCells = []; array.forEach(ship => takenCells.push(...ship.takenCells())) return new Set(takenCells).size === takenCells.length } function placeShipsOnBoard(array, board){ for (let i = 0; i < array.length; i++){ board.placeShip(array[i], array[i].startCoord) } } return ( <> {/* 遍历playerBoard生成10x10网格的逻辑 */} </> ) }
核心改动说明
- 移除了依赖
playerShipsArray的useEffect,彻底避免状态更新触发的循环逻辑 - 新增
generateValidShips函数,在单次函数调用内完成船组生成和碰撞校验,直到拿到合法船组才返回,完全不需要依赖组件重渲染来重试 - 棋盘更新时先创建新的board实例,更新完成后再调用
setPlayerBoard替换旧状态,符合React不可变状态的更新要求,能正常触发组件重渲染同步页面内容
内容的提问来源于stack exchange,提问作者Minh Nguyen
相关产品推荐
相关产品推荐

