UseState 不会更新组件各处的值

问题描述 投票:0回答:1

我正在尝试构建一个 React Web 应用程序,让人们能够解决国际象棋难题。我使用 chessground 库来渲染棋盘,并使用 chess.js 库来处理国际象棋逻辑。

我创建了一个组件 Board.tsx 来在我的 React 项目中使用 chessground 库。这是它的代码:

import React from 'react';
import { Chessground } from 'chessground';
import { Config } from 'chessground/config';
import { Chess } from 'chess.js';
import './Board.css';
import * as cg from 'chessground/types';

interface BoardProperties {
  fen: cg.FEN; // chess position in Forsyth notation
  orientation: cg.Color; // board orientation: 'white' | 'black'
  game?: Chess;
  turnColor?: cg.Color;
  coordinates?: boolean;
  viewOnly?: boolean;
  disableContextMenu?: boolean;
  highlight?: {
    lastMove?: boolean;
    check?: boolean;
  };
  animation?: {
    enabled?: boolean;
    duration?: number;
  };
  draggable?: {
    enabled?: boolean;
    distance?: number;
    autoDistance?: boolean;
    showGhost?: boolean;
    deleteOnDropOff?: boolean;
  };
  events?: {
    change?: () => void;
    move?: (orig: cg.Key, dest: cg.Key, capturedPiece?: cg.Piece) => void;
    dropNewPiece?: (piece: cg.Piece, key: cg.Key) => void;
    select?: (key: cg.Key) => void;
    insert?: (elements: cg.Elements) => void;
  };
}

class Board extends React.Component<BoardProperties> {
  private boardRef: React.RefObject<HTMLDivElement>;
  private groundInstance: ReturnType<typeof Chessground> | null;

  constructor(props: BoardProperties) {
    super(props);
    this.boardRef = React.createRef();
    this.groundInstance = null;
  }

  // Get allowed moves
  getLegalMoves() {
    const dests = new Map();
    const { game } = this.props;
    
    if (game) {
      let allLegalMoves = game.moves({ verbose: true });

      allLegalMoves.forEach((move) => {
        if (!dests.has(move.from)) {
          dests.set(move.from, []);
        }
        dests.get(move.from).push(move.to);
      });
    }
    return dests;
  }

  // Make a move 
  makeMove(from: cg.Key, to: cg.Key) {
    if (this.groundInstance) {
      this.groundInstance.move(from, to);
      console.log(`Move executed: ${from} -> ${to}`);
    }
  }

  componentDidMount() {
    if (this.boardRef.current) {
      const config: Config = {
        fen: this.props.fen,
        orientation: this.props.orientation,
        coordinates: this.props.coordinates ?? true,
        turnColor: this.props.turnColor || 'white',
        viewOnly: this.props.viewOnly || false,
        disableContextMenu: this.props.disableContextMenu || true,
        animation: this.props.animation || { enabled: true, duration: 500 },
        movable: {
          color: 'both',
          free: false,
          dests: this.getLegalMoves(),
          showDests: true,
        },
        highlight: this.props.highlight || {},
        events: this.props.events || {},
      };

      this.groundInstance = Chessground(this.boardRef.current, config);
    }
  }

  componentDidUpdate(prevProps: BoardProperties) {
    if (prevProps.fen !== this.props.fen) {
      if (this.groundInstance) {
        this.groundInstance.set({ fen: this.props.fen,  movable: {
            color: 'both',
            free: false,
            dests: this.getLegalMoves(),
            showDests: true,
          },});
      }
    }
  }

  componentWillUnmount() {
    if (this.groundInstance) {
      this.groundInstance.destroy();
    }
  }

  render() {
    return (
      <div>
        <div ref={this.boardRef} style={{ width: '400px', height: '400px' }}></div>
      </div>
    );
  }
}

export default Board;

我还创建了一个 React 组件 PuzzlesPage.js 来实现我的想法。在解释它的工作原理之前,我将先展示它的代码:

import Board from "./board/Board.tsx";
import { useEffect, useState, useRef } from 'react';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { Chess } from 'chess.js';

const DEFAULT_FEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';

export default function PuzzlesPage() {
    const [fen, setFen] = useState(DEFAULT_FEN);
    const [boardOrientation, setBoardOrientation] = useState('white');
    const [correctMoves, setCorrectMoves] = useState([]);
    const [game, setGame] = useState(new Chess(fen));
    const [currentMoveIndex, setCurrentMoveIndex] = useState(0);
    const [isUserTurn, setIsUserTurn] = useState(false);
    const [isComputerMove, setIsComputerMove] = useState(false); 

    const boardRef = useRef(null);

    const makeAIMove = () => {
        console.log('makeAIMove called');
        console.log('currentMoveIndex:', currentMoveIndex);
        console.log('correctMoves:', correctMoves);

        if (currentMoveIndex < correctMoves.length) {
            const move = correctMoves[currentMoveIndex];
            console.log('Computer move:', move);
            const [orig, dest] = [move.slice(0, 2), move.slice(2, 4)];

            if (game.move({ from: orig, to: dest })) {
                console.log('Computer move is valid');
                setFen(game.fen());
                boardRef.current.makeMove(orig, dest);
                setCurrentMoveIndex((prevIndex) => prevIndex + 1);
                setIsUserTurn(true);
                setIsComputerMove(false);
                console.log('User turn set to true, computer move executed');
            } else {
                console.log('Invalid computer move');
            }
        } else {
            console.log('currentMoveIndex ', currentMoveIndex, ' is >= correctMoves.length ', correctMoves.length);
        }
    };


    const handleUserMove = (orig, dest) => {
        console.log('User move:', orig, dest);
        console.log('isUserTurn:', isUserTurn);
        console.log('isComputerMove:', isComputerMove);
        
        if (!isUserTurn || currentMoveIndex >= correctMoves.length) {
            console.log('Not user\'s turn or moves are done');
            return;
        }

        const correctMove = correctMoves[currentMoveIndex];
        console.log('Expected user move:', correctMove);
        if (orig + dest === correctMove) {
            console.log('User move is correct');
            if (game.move({ from: orig, to: dest })) {
                setFen(game.fen());
                setCurrentMoveIndex((prevIndex) => prevIndex + 1);
                setIsUserTurn(false);
                setIsComputerMove(true);
                console.log('User turn set to false, computer turn will follow');

                
                if (currentMoveIndex + 1 < correctMoves.length) {
                    console.log('Preparing for next computer move');
                    setTimeout(() => makeAIMove(), 500);
                } else {
                    console.log('Puzzle solved');
                    toast.success('Puzzle solved!');
                }
            }
        } else {
            console.log('User move is incorrect');
            toast.error('User move is incorrect');
            boardRef.current.groundInstance.set({ fen: game.fen() });
        }
    };


    function getRandomPuzzle() {
        console.log('Fetching new puzzle');
        fetch('http://localhost:5000/api/random-puzzle')
            .then((response) => response.json())
            .then((data) => {
                console.log('Puzzle received:', data);
                setFen(data.fen);

                
                if (data.moves && data.moves.trim()) {
                    const moves = data.moves.split(' ').filter(move => move); 
                    console.log('Parsed moves:', moves);
                    setCorrectMoves(moves);
                } else {
                    console.error('No valid moves found in the response');
                    setCorrectMoves([]);
                }

                setGame(new Chess(data.fen));
                setBoardOrientation(data.fen.split(' ')[1] === 'b' ? 'white' : 'black');
                setCurrentMoveIndex(0);
                setIsUserTurn(false);
                setIsComputerMove(false); 
            })
            .catch((error) => {
                console.error('Error fetching puzzle:', error);
            });
    }

    
    useEffect(() => {
        if (correctMoves.length > 0) {
            console.log('First computer move after puzzle load');
            setTimeout(() => makeAIMove(), 500);
        }
    }, [correctMoves]);

    return (
        <div>
            <Board
                ref={boardRef}
                fen={fen}
                game={game}
                orientation={boardOrientation}
                events={{
                    move: (orig, dest) => {
                        
                        console.log('Move event triggered:', orig, dest);
                        console.log('isUserTurn before handling:', isUserTurn);
                        console.log('isComputerMove before handling:', isComputerMove);
                        
                        
                        if (isComputerMove) {
                            console.log('Ignoring computer move event:', orig, dest);
                            setIsComputerMove(false); 
                            return;
                        }
                        
                        handleUserMove(orig, dest); 
                    },
                }}
            />
            <button
                onClick={getRandomPuzzle}
                style={{ marginTop: '20px', padding: '10px 20px', fontSize: '16px' }}
            >
                New puzzle
            </button>
            <ToastContainer />
        </div>
    );
}

工作原理:

当用户按下按钮时,将从服务器加载一个新的谜题。这是服务器响应的示例:

{
    "id": 3684723,
    "puzzle_id": "ZgWE3",
    "fen": "8/5pk1/8/3rq2p/6p1/6P1/1pQ2PP1/1R4K1 w - - 0 43",
    "moves": "b1b2 e5e1 g1h2 d5d1 c2d1 e1d1",
    "rating": 1681,
    "rating_deviation": 84,
    "popularity": 79,
    "nb_plays": 39,
    "themes": "crushing endgame long quietMove",
    "game_url": "https://lichess.org/5kJUutVK#85",
    "opening_tags": null
}

然后它将“移动”转换为移动数组,并在创建

useEffect
数组后在
makeAIMove()
中调用
correctMoves
。当
makeAIMove()
被调用时,如果从
correctMoves
开始第一步移动并开始等待用户的移动。当用户进行移动时,它会检查此移动是否在计算机在
correctMoves
中的
handleUserMove()
移动之后进行,如果是,则再次调用
makeAIMove()
等等,直到计算机和用户从
correctMoves
开始进行所有移动。

问题:

计算机通过

boardRef.current.makeMove(orig, dest);
做出的动作和用户自己做出的动作都会调用Board组件中的events.move事件。仅当用户移动以获取其移动坐标时,我才需要处理此事件。我使用布尔标志
isUserTurn
isComputerMove
来决定哪个动作称为事件。但问题是,标志
isUserTurn
isComputerMove
并未在组件中的所有位置更新,因此在我的代码中的某个位置(例如,在 events.move 中)它们包含旧值。

我可以向您展示控制台日志,这可能有助于您理解我的问题:

*New puzzle button has been pressed*

Fetching new puzzle
PuzzlesPage.js:90 Puzzle received: {id: 4823889, puzzle_id: 'qmZAy', fen: '8/p1p2p2/8/2p1kP2/4P3/1PrP3R/3K4/8 b - - 2 34', moves: 'c3b3 d3d4 c5d4 h3b3', rating: 875, …}
PuzzlesPage.js:96 Parsed moves: (4) ['c3b3', 'd3d4', 'c5d4', 'h3b3']
PuzzlesPage.js:117 First computer move after puzzle load
PuzzlesPage.js:21 makeAIMove called

*Then computer makes its first move*

currentMoveIndex: 0
PuzzlesPage.js:23 correctMoves: (4) ['c3b3', 'd3d4', 'c5d4', 'h3b3']
PuzzlesPage.js:27 Computer move: c3b3
PuzzlesPage.js:31 Computer move is valid
Board.tsx:73 Move executed: c3 -> b3
PuzzlesPage.js:37 User turn set to true, computer move executed
PuzzlesPage.js:132 Move event triggered: c3 b3 

*Here it should decide that event was called by AI move*

PuzzlesPage.js:133 isUserTurn before handling: false              (Why?)
PuzzlesPage.js:134 isComputerMove before handling: false 
PuzzlesPage.js:48 User move: c3 b3

*It decided that it was user's turn and took coords and called handleUserMove()*

PuzzlesPage.js:49 isUserTurn: false              (Why?)
PuzzlesPage.js:50 isComputerMove: false
PuzzlesPage.js:53 Not user's turn or moves are done

*Then user makes a random move at board*

Move event triggered: h3 h6
PuzzlesPage.js:133 isUserTurn before handling: false
PuzzlesPage.js:134 isComputerMove before handling: false
PuzzlesPage.js:48 User move: h3 h6 

*It decided that it was user's turn and took coords and called handleUserMove() but it's too late*

PuzzlesPage.js:49 isUserTurn: false
PuzzlesPage.js:50 isComputerMove: false
PuzzlesPage.js:53 Not user's turn or moves are done

可能还有另一个问题导致该行为,但除了错误的值更新之外,我找不到其他原因。

reactjs react-hooks chess
1个回答
0
投票

您的

events.move
函数对
PuzzlesPage
组件中的状态值有一个过时的闭包。
events.move
函数仅在创建时知道其周围范围内的变量/状态。由于您仅使用
Chessground
中的
componentDidMount
实例注册此函数,因此您只注册了在
events.move
的第一个渲染中创建的第一个
PuzzlesPage
函数(它只知道在该渲染的渲染中创建的初始状态变量)范围)。

您有几个选项可以解决此问题:

  • 使用 Chessground React 包装器:https://github.com/react-chess/chessground,以便在配置更改时将新的配置对象与最新功能一起使用

  • 重置您的

    Chessground
    实例以使用最新的事件对象:

    componentDidUpdate(prevProps) {
        // ... existing logic ...
        if (prevProps.events !== this.props.events) {
          this.groundInstance.set({ events: this.props.events });
        }
    }
    

    请注意,当您的事件函数在每次重新渲染时发生更改时,此操作会在每次重新渲染时触发(您可以通过记住回调来更改)。

© www.soinside.com 2019 - 2024. All rights reserved.