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

React Hooks问题:兄弟组件互控状态报错求助

问题:React组件渲染时更新父组件状态报错

开发React Hooks国际象棋应用,包含Board和MoveHistory两个兄弟组件,MoveHistory支持回退到之前的走法。当Board通过回调向MoveHistory传递走法时,出现错误:
Cannot update a component ('App') while rendering a different component ('MoveHistory')

核心代码

App.tsx(父组件)

...
const STARTING_FEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'

function App() {
  const [FEN, setFEN] = useState(STARTING_FEN);
  const [moveHistory, setMoveHistory] = useState<string[]>([]);
  const [fenHistory, setFenHistory] = useState<string[]>([]);

  // 回退到指定步数的游戏状态
  function onRewind(target: number) {
    setFEN(fenHistory[target]);
    setMoveHistory(moveHistory.slice(0, target));
    setFenHistory(fenHistory.slice(0, target));
  }

  // 新增走法到历史记录
  function onMove(move: string, FEN: string) {
    setMoveHistory([...moveHistory, move]);
    setFenHistory([...fenHistory, FEN]);
  }

  return (
    <div className='app'>
      <Board FEN={FEN} onMove={onMove} />
      <MoveHistory moves={moveHistory} onRewind={onRewind} />
    </div>
  );
}

export default App;

Board.tsx(兄弟组件1)

...
interface BoardProps {
  FEN: string;
  onMove: Function;
}

function Board(props: BoardProps) {
  const splitFEN = props.FEN.split(' ');
  const [squares, setSquares] = useState(generateSquares(splitFEN[0]));
  const [lastClickedIndex, setLastClickedIndex] = useState(-1);
  const [activeColor, setActiveColor] = useState(getActiveColor(splitFEN[1]));
  const [castleRights, setCastleRights] = useState(getCastleRights(splitFEN[2]));
  const [enPassantTarget, setEnPassantTarget] = useState(getEnPassantTarget(splitFEN[3]));
  const [halfMoves, setHalfMoves] = useState(parseInt(splitFEN[4]));
  const [fullMoves, setFullMoves] = useState(parseInt(splitFEN[5]));

  ...

  // 处理棋子移动(用户点击棋盘格时触发)
  function onSquareClicked(index: number) {
    ... // 判断棋子移动位置的逻辑
    {      
      props.onMove(moveName, getFEN())
    }
  }

  ...

  // 生成当前棋盘状态的FEN字符串
  function getFEN(): string {
    ... // 将棋盘状态转换为字符串的逻辑
    return `${pieceString} ${activeString} ${castleString} ${enPassantString} ${halfMoves} ${fullMoves}`;
  }

  return (
    <div className='board'>
      {[...Array(BOARD_SIZE)].map((e, rank) => {
        return (
          <div key={rank} className='row'>
            {squares.slice(rank * BOARD_SIZE, BOARD_SIZE + rank * BOARD_SIZE).map((square, file) => {
              return (
                <Square
                  key={file}
                  index={coordsToIndex(rank, file)}
                  pieceColor={square.pieceColor}
                  pieceType={square.pieceType}
                  style={square.style}
                  onClick={onSquareClicked}
                />
              );
            })}
          </div>
        )
      })};
    </div>
  );
}
  
export default Board;

MoveHistory.tsx(兄弟组件2)

...
interface MoveHistoryProps {
  moves: string[],
  onRewind: Function;
}

function MoveHistory(props: MoveHistoryProps) {
  return (
    <div className='move-history'>
      <div className='header'>
        Moves
      </div>
      <div className='move-list'>
        {_.chunk(props.moves, 2).map((movePair: string[], index: number) => {
          return (
            <div className='move-pair' key={index}>
              <span>{`${index + 1}.`}</span>
              <span onClick={props.onRewind(index * 2)}>{movePair[0]}</span>
              <span onClick={props.onRewind(index * 2 + 1)}>{movePair[1] || ""}</span>
            </div>
          )
        })}
      </div>
    </div>
  )
}
  
export default MoveHistory;

错误原因

问题出在MoveHistory组件的onClick绑定方式:

<span onClick={props.onRewind(index * 2)}>{movePair[0]}</span>

这里直接调用了props.onRewind(),而非传递一个点击时才执行的函数。组件渲染阶段就会触发onRewind,进而调用App中的setState,导致在渲染MoveHistory的同时更新App的状态,触发React的警告。

解决方案

1. 修复MoveHistory的onClick绑定

用匿名函数包裹onRewind调用,确保只有点击时才执行:

function MoveHistory(props: MoveHistoryProps) {
  return (
    <div className='move-history'>
      <div className='header'>
        Moves
      </div>
      <div className='move-list'>
        {_.chunk(props.moves, 2).map((movePair: string[], index: number) => {
          return (
            <div className='move-pair' key={index}>
              <span>{`${index + 1}.`}</span>
              {/* 匿名函数包裹,点击时触发 */}
              <span onClick={() => props.onRewind(index * 2)}>{movePair[0]}</span>
              <span onClick={() => props.onRewind(index * 2 + 1)}>{movePair[1] || ""}</span>
            </div>
          )
        })}
      </div>
    </div>
  )
}

2. 额外优化(可选)

  • 完善TypeScript类型定义:替换模糊的Function类型为具体函数类型,提升代码健壮性:
// BoardProps
interface BoardProps {
  FEN: string;
  onMove: (move: string, fen: string) => void;
}

// MoveHistoryProps
interface MoveHistoryProps {
  moves: string[],
  onRewind: (target: number) => void;
}
  • 同步Board组件状态:当前Board内部状态仅从初始FEN初始化,回退操作后不会同步更新。添加useEffect监听props.FEN变化,同步内部状态:
useEffect(() => {
  const splitFEN = props.FEN.split(' ');
  setSquares(generateSquares(splitFEN[0]));
  setActiveColor(getActiveColor(splitFEN[1]));
  setCastleRights(getCastleRights(splitFEN[2]));
  setEnPassantTarget(getEnPassantTarget(splitFEN[3]));
  setHalfMoves(parseInt(splitFEN[4]));
  setFullMoves(parseInt(splitFEN[5]));
}, [props.FEN]);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 10:51:09