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
相关产品推荐
相关产品推荐

