React开发扫雷游戏遇变量重复声明致无限循环,如何声明不可变变量?
问题分析与解决方案
核心问题
- 全局变量失效:外部声明的
let nrbomb = 40,组件每次重渲染时,虽然变量本身不会被重新声明,但组件内inc == 1 && rendergame == 0分支会重复执行,加上循环中直接修改全局变量,会导致状态与变量值不同步,且全局变量不被React追踪,无法触发正确渲染。 - 渲染阶段更新状态导致无限循环:若用
useState存储nrbomb,在渲染逻辑(while循环)里调用setState,每次状态更新都会触发组件重渲染,进而再次进入循环,形成无限循环。 - 对象引用共享问题:你创建的
mine_grid中,所有格子都引用同一个square对象,修改任意一个格子的bomb或viz属性,会导致所有格子属性同时变化,属于严重逻辑错误。
解决步骤
1. 修复格子对象引用问题
创建网格时,每个格子都要生成新对象,避免复用同一个引用:
// 初始化网格,每个格子都是独立对象 const initialGrid = Array(n).fill().map(() => Array(n).fill().map(() => ({ bomb: 0, viz: 0 })) )
2. 用useEffect处理炸弹生成逻辑
不要在渲染阶段直接更新状态,改用useEffect监听inc变化,一次性完成炸弹生成并更新状态:
import { useState, useEffect } from 'react' import './App.css' function App() { const n = 12 const totalBombs = 40 // 炸弹数量作为常量,无需全局/state存储 const initialGrid = Array(n).fill().map(() => Array(n).fill().map(() => ({ bomb: 0, viz: 0 })) ) const [inc, change_inc] = useState(0) const [mine_grid, change_mine_grid] = useState(initialGrid) const [cx, change_cx] = useState(0) const [cy, change_cy] = useState(0) const [rendergame, change_render_game] = useState(0) // 监听inc变化,触发炸弹生成 useEffect(() => { if (inc === 1 && rendergame === 0) { // 深拷贝网格,避免直接修改原状态 const newGrid = JSON.parse(JSON.stringify(mine_grid)) let remainingBombs = totalBombs while (remainingBombs > 0) { const x = Math.floor(Math.random() * n) const y = Math.floor(Math.random() * n) // 跳过已点击格子和已有炸弹的格子 if (newGrid[x][y].viz === 0 && newGrid[x][y].bomb === 0) { newGrid[x][y].bomb = 1 remainingBombs-- } } // 一次性更新网格和状态,避免多次重渲染 change_mine_grid(newGrid) change_render_game(1) } }, [inc, rendergame, mine_grid, n, totalBombs]) if (inc === 0) { return ( <div className="all"> <div className="grid_container"> {mine_grid.map((arr, i) => { return arr.map((e, j) => { return ( <div key={`${i}-${j}`} // 添加唯一key,避免React渲染错误 style={{ background: 'green', width: '50px', height: '50px', margin: '5px', }} ></div> ) }) })} </div> <h6>for x:</h6> <input type="number" value={cx} onChange={(e) => change_cx(Number(e.target.value))} ></input> <h6>for y:</h6> <input type="number" value={cy} onChange={(e) => change_cy(Number(e.target.value))} ></input> <button type="button" onClick={() => { if (cx >=0 && cx < n && cy >=0 && cy < n) { const newGrid = JSON.parse(JSON.stringify(mine_grid)) newGrid[cx][cy].viz = 1 change_mine_grid(newGrid) change_inc(1) } }} > Submit move </button> </div> ) } // 生成炸弹后的游戏界面 return ( <div className="grid_container"> {mine_grid.map((arr, i) => ( arr.map((e, j) => ( <div key={`${i}-${j}`} style={{ background: e.viz ? (e.bomb ? 'red' : 'gray') : 'green', width: '50px', height: '50px', margin: '5px', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '24px' }} > {e.viz && e.bomb ? '💣' : ''} </div> )) ))} </div> ) } export default App
3. 关键说明
- 避免渲染阶段更新状态:
useEffect是React处理副作用的正确方式,仅在依赖项变化时执行,不会重复触发循环。 - 临时变量替代全局/state变量:炸弹剩余数量
remainingBombs是生成逻辑内的临时变量,生成完成后无需留存,不需要存入state或全局变量。 - 状态不可变性:修改网格时必须创建新的数组/对象(如深拷贝),不能直接修改原状态对象,否则React无法检测状态变化,不会触发重渲染。
- 添加唯一key:列表渲染时必须给每个元素加唯一key,避免React渲染错误和性能问题。
内容的提问来源于stack exchange,提问作者No0bYebY
相关产品推荐
相关产品推荐

