You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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节点,原有节点上已经被修改的类名就会被残留下来。
  • 你用到的setTimeout hack之所以生效,是因为你先把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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 04:45:04