React开发Connect4四子棋时 setState重置网格出现undefined问题
问题根因
1. grid 为 undefined 的直接原因
gridHtml 函数没有返回值,执行后默认返回 undefined。你在 reset 方法中直接将 grid 赋值为 this.gridHtml() 的返回值,自然会将 state.grid 覆盖为 undefined。同时 gridHtml 内部还主动调用了一次 setState,会导致重置时连续触发两次状态更新,逻辑完全混乱。
2. 重置后棋子残留的核心原因
- 你违反了React的状态设计原则:
state应当存储原始业务数据,而非渲染好的JSX节点。你把JSX直接存在state里,下棋时直接操作真实DOM修改类名/属性,这些修改不会同步到state中存储的JSX结构里。 - 渲染网格时你使用了索引作为
key,React做虚拟DOM Diff时会默认复用相同key的DOM节点,原有节点上已经被修改的类名就会被残留下来。 - 你用到的
setTimeouthack之所以生效,是因为你先把grid改为文本,卸载了所有原有网格DOM节点,延迟后重新渲染时会创建全新的DOM节点,自然就没有了之前的类名残留,本质是绕开了DOM复用逻辑,属于典型的非规范实现。
3. 其他冗余问题
componentWillMount 是已经被官方废弃的生命周期钩子,你在该钩子和componentDidMount中重复调用gridHtml,会导致初始化时连续触发两次不必要的重渲染。
正确实现方案
核心思路是:state只存网格状态数据,所有渲染逻辑在render阶段执行,所有状态修改都通过setState更新数据实现,完全不要直接操作DOM。
import React from 'react'; class Grid extends React.Component { constructor(props) { super(props); // state只存原始数据,不存JSX this.state = { player: "red", isGameOver: false, gamestarts: false, // 6行7列的网格数据,null代表空位置 grid: Array(6).fill(null).map(() => Array(7).fill(null)) }; this.findLastEmptyColl = this.findLastEmptyColl.bind(this); this.onMouseEnter = this.onMouseEnter.bind(this); this.onMouseLeave = this.onMouseLeave.bind(this); this.onClick = this.onClick.bind(this); this.checkForWinner = this.checkForWinner.bind(this); this.reset = this.reset.bind(this); }; // 下棋逻辑修改为更新state.grid数据,不要直接操作DOM onClick(e) { if (this.state.isGameOver) return; const col = parseInt(e.target.dataset.col); // 找到当前列最后一个空行 const row = this.findLastEmptyColl(col); if (row === -1) return; // 拷贝网格数据更新 const newGrid = this.state.grid.map(row => [...row]); newGrid[row][col] = this.state.player; this.setState({ grid: newGrid, gamestarts: true, player: this.state.player === "red" ? "yellow" : "red" }, () => { // 状态更新完成后检查是否获胜 this.checkForWinner(row, col, newGrid); }); } // 把gridHtml改为纯函数,根据state数据生成JSX,不修改state renderGrid() { const { grid } = this.state; return grid.map((row, rowIndex) => { return ( <div key={rowIndex} className="row"> {row.map((cellVal, colIndex) => { // 类名完全由state数据决定 const className = `col ${cellVal || "empty"}`; return ( <div key={colIndex} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onClick={this.onClick} className={className} data-col={colIndex} data-row={rowIndex}> </div> ); })} </div> ); }); } // 重置逻辑非常简单,直接把网格数据恢复为初始状态即可 reset() { this.setState({ grid: Array(6).fill(null).map(() => Array(7).fill(null)), isGameOver: false, gamestarts: false, player: "red" }) } render() { return ( <> {!this.state.gamestarts && <h4>Connect 4 - Player {this.state.player} begins!</h4>} {this.state.gamestarts && <h4>Player {this.state.player} </h4>} {(this.state.isGameOver && !this.state.gamestarts) && <h4>Player {this.state.player} has won</h4>} <div id="board"> {/* 直接调用渲染方法即可,不需要从state取JSX */} {this.renderGrid()} </div> <div> <button style={{margin: "30px"}} onClick={this.reset}>Reset</button> </div> </> ) } } export default Grid;
内容的提问来源于stack exchange,提问作者StJungDev
相关产品推荐
相关产品推荐

