React Hooks实现井字棋:状态更新逻辑原理疑惑求助
问题:React Hooks中直接修改state数组内对象属性为何能更新状态?
我用React Hooks开发井字棋游戏,state里有个包含9个对象的数组,每个对象有num(取值0-8)和val(初始为空字符串)属性。点击格子时,期望根据当前活跃玩家将对应格子的val设置为"X"或"O"。
我原本以为必须用扩展运算符生成新数组来更新state,但实际直接通过方括号选中目标对象修改val后,状态就完成了更新,注释掉扩展运算符相关代码也能正常运行,无法理解该状态更新的原理,希望得到解释。
代码示例
App.js
import React from 'react'; import { useState } from 'react' import Square from './components/Square' // possible winning rows // switch between player 1 and 2 // reset button const App = () => { const [playerX, setPlayerX] = useState([]) const [playerO, setPlayerO] = useState([]) // false = playerX const [activePlayer, setActivePlayer] = useState(false) const [square, setSquare] = useState([ { num: 0, val: '' }, { num: 1, val: '' }, { num: 2, val: '' }, { num: 3, val: '' }, { num: 4, val: '' }, { num: 5, val: '' }, { num: 6, val: '' }, { num: 7, val: '' }, { num: 8, val: '' } ]) const clickHandler = (squareItem) => { // square clicked // let newSquares = [...square] const squareUpdate = square[squareItem.num] // check active player if(activePlayer) { // push square num to player array setPlayerO([...playerO, squareItem.num ]) // change square value squareUpdate.val = "O" // update state array with changed // setSquare([...newSquares]) setActivePlayer(!activePlayer) }else { // add square num to player array setPlayerX([...playerX, squareItem.num ]) // change square value squareUpdate.val = "X" // update state array with changed // setSquare([...newSquares]) setActivePlayer(!activePlayer) } } return ( <div id="game"> <h1>Tic - Tac - Toe</h1> <div id="grid"> {square.map((squareItem, index) => { return <Square activePlayer={activePlayer} squareItem={squareItem} clickHandler={clickHandler} /> })} </div> </div> ) } export default App
Square.js
import React from 'react' const Square = ({ squareItem, clickHandler }) => { return ( <div onClick={() => clickHandler(squareItem)} className="square" value={squareItem.value}> {squareItem.num} </div> ) } export default Square
原理解释
引用类型的特性:
square是数组类型,数组内的元素是对象——这都属于引用类型。React的useState保存的是这个数组的引用,你直接修改数组内对象的val属性时,并没有改变数组本身的引用,但对象的内部值已经发生了变化。重渲染的触发条件:你虽然没调用
setSquare,但调用了setPlayerX/setPlayerO和setActivePlayer,这两个set函数会触发组件重渲染。重渲染时,React会读取square数组的当前状态,而你之前已经修改了数组内对象的属性,所以新的渲染会显示更新后的值。这是错误的写法:虽然看起来能运行,但违反了React的不可变状态原则,会导致:
- React的状态对比机制失效,可能出现后续状态更新不触发渲染的问题
- 调试难度增加,无法追踪状态变化的历史轨迹
- 组件状态来源混乱,容易引发难以排查的bug
正确的写法
严格遵循不可变原则,创建新的数组和对象来更新状态:
const clickHandler = (squareItem) => { // 生成新的square数组,仅修改目标对象的val属性 const newSquare = square.map(item => { if (item.num === squareItem.num) { return { ...item, val: activePlayer ? "O" : "X" }; } return item; }); if (activePlayer) { setPlayerO(prev => [...prev, squareItem.num]); } else { setPlayerX(prev => [...prev, squareItem.num]); } setSquare(newSquare); setActivePlayer(!activePlayer); }
内容的提问来源于stack exchange,提问作者fluffy-lionz
相关产品推荐
相关产品推荐

