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

