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

React井字棋开发:子组件点击后父组件数组未更新求助

井字棋React项目:点击方块后父组件state数组始终为0的问题分析

我正在学习React,开发井字棋项目时遇到了问题:点击方块组件时,方块的显示值会变化,但点击log按钮打印父组件的state数组时,该数组始终全为0。相关代码如下:

import './App.css';
import { useState } from "react"

const X = 1;
const O = -1;
const EMPTY = 0;

var Square = ({idx, click}) => 
{
    let [val, setVal] = useState(EMPTY);
    return (
        <button onClick={() => {click(setVal, idx);}}>{val}</button>
    )
}

var Logger = ({state}) =>
{
    return (
        <button onClick={() => {console.log(state);}}>log</button>
    )
}

var App = () =>
{
    let [turn, setTurn] = useState(X);
    let state = new Array(9).fill(EMPTY);
    let squares = new Array(9);

    let click = (setValFunc, idx) => 
    {
        setTurn(-turn);
        setValFunc(turn);
        state[idx] = turn;
    }

    for (let i = 0 ; i < 9 ; i++)
    {
        squares[i] = (<Square click={click} idx={i}/>);
    }

    return (
        <>
        <Logger state={state} />
        <div>
            {squares}
        </div>
        </>
    )
}

export default App;

你忽略的三个核心关键点:

  • 父组件的state不是React状态变量:你在App里用let state = new Array(9).fill(EMPTY);定义的是普通JavaScript变量,不是通过useState声明的React状态。React组件每次重新渲染时,这个变量都会被重新初始化,所以log出来永远是初始的全0数组。
  • 修改普通变量不会触发组件重渲染:就算你在click函数里改了state[idx] = turn;,但这不是React状态的更新方式,不会触发App组件重新渲染,Logger组件拿到的始终是第一次渲染时的旧数组。
  • 状态分散管理违背React设计思路:每个Square组件自己用useState维护val,父组件没有统一管理整个棋盘的状态,导致父组件无法同步获取到最新的棋盘数据,这完全不符合React单向数据流的设计原则。

修正方案(核心代码调整):

把棋盘状态统一交给父组件用useState管理,Square组件不再维护自身状态,只负责展示和触发父组件的回调:

import './App.css';
import { useState } from "react"

const X = 1;
const O = -1;
const EMPTY = 0;

// Square组件去掉内部状态,使用父组件传入的值和回调
var Square = ({val, onClick}) => 
{
    // 把数值转成直观的X/O显示
    const displayVal = val === X ? 'X' : val === O ? 'O' : '';
    return (
        <button onClick={onClick}>{displayVal}</button>
    )
}

var Logger = ({state}) =>
{
    return (
        <button onClick={() => {console.log(state);}}>log</button>
    )
}

var App = () =>
{
    let [turn, setTurn] = useState(X);
    // 用useState声明棋盘状态,这才是React能识别的状态变量
    let [boardState, setBoardState] = useState(new Array(9).fill(EMPTY));
    let squares = new Array(9);

    let handleSquareClick = (idx) => 
    {
        // 先判断当前位置是否为空,防止重复点击
        if (boardState[idx] !== EMPTY) return;
        // 复制原数组并修改,避免直接修改状态(React状态不可变)
        const newBoard = [...boardState];
        newBoard[idx] = turn;
        setBoardState(newBoard);
        // 切换回合
        setTurn(-turn);
    }

    for (let i = 0 ; i < 9 ; i++)
    {
        // 给Square传入当前位置的值和点击回调
        squares[i] = (<Square val={boardState[i]} onClick={() => handleSquareClick(i)} />);
    }

    return (
        <>
        <Logger state={boardState} />
        <div>
            {squares}
        </div>
        </>
    )
}

export default App;

修正后的优势:

  • 父组件统一管理棋盘状态,保证数据唯一来源,不会出现状态不一致的情况
  • 修改React状态会自动触发组件重新渲染,Logger组件能实时拿到最新的棋盘数据
  • 完全遵循React单向数据流的设计,子组件只负责展示和传递用户操作,状态逻辑集中在父组件,代码更易维护

内容的提问来源于stack exchange,提问作者yoyobara

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 16:42:51