如何在调用 this.setState() 的另一个函数内的函数中访问组件的状态?

How do I access a component's state in a function that is inside another function that calls this.setState()?

我正在构建我的第一个 React 应用程序,遇到了这种情况,我必须在一个方法中调用 this.setState() 两次。其中一个调用也是在从该方法调用的嵌套函数内进行的。

在我的例子中,我正在构建一个扫雷游戏,我想要实现的是找出玩家在正确标记所有地雷后是否真的赢了。当 在每个包含地雷的单元格上设置标志 时玩家获胜,这样您就无法点击它。 为此,我创建了一个名为 checkIfWin() 的方法,该方法执行以下操作:

  1. 将棋盘数据信息(每个单元格及其属性)存储在一个数组中,并将剩余地雷的数量存储在一个计数器中。两者都是从名为 dataminesLeft
  2. 的两个变量中提取的
  3. 如果剩余 0 个地雷(用户标记的单元格与游戏中的地雷一样多),则比较两个数组(minesArray 包含板和 flagsArray 包含每个标志位置)
  4. 如果数组包含相同的信息(标志覆盖每个地雷),您就赢了。如果数组不同,请继续玩。

这个函数是从两个不同的地方调用的

  1. 当玩家点击最后一个不包含地雷的单元格并且每个包含旗帜的方块都被正确标记后
  2. 在玩家标记一个单元格并且剩余的地雷覆盖在板上后为 0 在第一种情况下没有问题,它工作正常。我检查状态中的 minesLeft 是否为 === 0,如果是,我调用方法 checkIfWin()。由于 minesLeft 仅在标记一个单元格后减少,而不是通过单击一个单元格,因此这里没有问题。

但是当玩家在一个单元格上标记后,我无法找到一个解决方案来声明玩家是否赢得了比赛。这是我的 flag() 逻辑:

  1. 检查它是否被覆盖(尚未被点击)以及玩家是否尚未获胜(以防止在游戏结束后点击瓷砖)
  2. 如果一个图块被覆盖并被标记,取消标记并更新计数器。
  3. 如果图块被覆盖,但没有标记,则用标记覆盖它并减少 minesLeft 计数器。
  4. minesLeft 为 1 并且 Player 标记了一个新单元格时,minesLleft 现在为 0,所以这是我必须检查是否获胜的时候。
  5. 现在 我想检查玩家是否赢了 为此,我必须调用 this.setState() 来更新 flagsArray 以及新标志在棋盘上的位置,然后调用 checkIfWin() - 从该方法中提取存储在 state 中的值。

这导致 this.setState() 调用 so that when I try to compare if minesLeft===0 inside checkIfWin() the value stored in state is the value state had before the last flagging (1 instead of 0), it didn't update because of React's stacking up logic for this.setState() calls. Also mentioned on JusticeMba article on medium

React may batch multiple setState() calls into a single update for performance.

那么我想知道的是(抱歉问题太长了)我是否有办法做到这一点?我实际上将此标记方法绑定为董事会 render() 内组件上的右键单击处理程序。代码将在下面。我不关心你是否知道代码或伪代码中的一些解决方案。我会同时考虑这两个问题,因为我似乎找不到合适的解决方法。

代码:

在Board.js render()

里面
 render() {
    const board = this.renderBoard(this.state.boardData);
    return (
        <div className={classes.board}>
            <div className={classes.gameInfo}>
                <h1>
                    {this.state.gameStatus}
                </h1>
                <span className={classes.info}>
                    Mines remaining: {this.state.mineCount}
                </span>
            </div>

            {board} //This is a Board component that holds Tiles to which the rightClickHandler() is binded as a prop

        </div>

管理 Tiles 标记的 rightClickHandler():

rightClickHandler(event, x, y) {
    event.preventDefault();  // prevents default behaviour such as right click
    const clickedTile = { ...this.state.boardData[x][y] }

     //ommit if revealed and if  game is ended
    if (!clickedTile.isRevealed && !(this.state.gameStatus === "YOU WON!")) {

        let minesLeft = this.state.mineCount;

        //if not revealed it can be flagged or not
        if (clickedTile.isFlagged) {

            //if flagged, unflag it
            clickedTile.isFlagged = false;
            minesLeft++;
        } else {

            //if not flagged, flag it
            clickedTile.isFlagged = true;
            minesLeft--;
        }

        //Update the state with new tile and check game status
        const updatedData = [...this.state.boardData];
        updatedData[x][y] = { ...clickedTile };

        this.setState({
            boardData: updatedData,
            mineCount: minesLeft,
        });

    //THIS IS WHERE I WOULD LIKE TO checkIfWin() 

    }

最后,checkIfWin():

//Check game progress and returns a boolean: true if win, false if not.  
checkIfWin() {

    //No need to create a new array, I'll just reference the one inside state
    const data = this.state.boardData;
    const minesLeft = this.state.mineCount;

    //If  flagged as many tiles as mines were initially
    if (minesLeft === 0) {

        //get mines as an array of positions
        const mineArray = this.getMines(data);

        //get flags as array of positions
        const flagArray = this.getFlags(data);

        if (JSON.stringify(mineArray) === JSON.stringify(flagArray)) {

            //if both arrays are equal, game is won
            return true;
        }

    } else {

        //if more than 0 mines are left return false
        return false;
    }
}

TL;DR:我正在尝试访问具有过时状态的组件 属性 的值,该状态应该从单击处理程序中的先前 this.setState() 调用更新render()

中的 JSX 组件

抱歉,篇幅太长了,我希望它是可以理解的,如果不是我可以编辑清楚。

假设我理解正确,您可以简单地将 checkIfWin 作为回调传递给您的 setState

rightClickHandler(event, x, y) {
    // Rest of your code...

    // Pass a callback to your setState
    this.setState({
        boardData: updatedData,
        mineCount: minesLeft,
    }, this.checkIfWin);

}

this.setState() 有一个回调,所以在上面你只是提供 运行.

的内容
this.setState(state, callback);

所以,上面发生的事情是:

this.setState({
    boardData: updatedData,
    mineCount: minesLeft,
},
() => {
    // Check if the player won or not
});

也可以这样写:

checkIfWin(){
    // Check if the player won or not
}

updateState(){

    this.setState(
        {
            boardData: updatedData,
            mineCount: minesLeft,
        }, 
        this.checkIfWin());
}

希望这是可以理解的。

Felipe Lanza and Nomi G approach and by understanding how react setState callback 工作之后,我得出了一个工作解决方案,如下所示:

  1. 在用户标记与棋盘中的地雷一样多的图块后 (minesLeft === 0),我使用 setState 回调和 checkIfWin() 来检查玩家是否赢了
  2. 我修改了 checkIfWin() 以在游戏获胜时更新状态,而不是返回布尔值。

这是我的代码的样子: 内部 rightClickHandler():

rightClickHandler(event, x, y) {
   // code...

   // Pass a callback to your setState
   if(minesLeft === 0) {

        //If user flagged possible last tile containing a mine, check if won with a setState callback to checkIfWin()
        this.setState(
         {
            boardData: updatedData,
            mineCount: minesLeft,
         }
     ,() => {
                this.checkIfWin(this.state.mineCount);
            });
 }  else {
    //Rest of logic
}

这就是我调整后的 checkIfWin() 现在的样子:

checkIfWin() {
    const data = this.state.boardData;
    const minesLeft = this.state.mineCount;

    //If  flagged as many tiles as mines were initially
    if (minesLeft === 0) {

        //get mines as an array of positions
        const mineArray = this.getMines(data);

        //get flags as array of positions
        const flagArray = this.getFlags(data);

        if (JSON.stringify(mineArray) === JSON.stringify(flagArray)) {

            //if both arrays are equal, game is won. Update gameStatus
            this.setState({
                gameStatus: "YOU WON!"
            });
        }
    } 
}