简体   繁体   English

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

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

I'm building my first react app and came across this situation in which I have to call this.setState() twice within a method.我正在构建我的第一个 react 应用程序并遇到了这种情况,在这种情况下,我必须在一个方法中调用 this.setState() 两次。 One of those calls is made inside a nested function called from the method as well.其中一个调用也是在从该方法调用的嵌套函数中进行的。

In my case, I'm building a minesweeper game, and what I'm trying to achieve is to find out if a player has actually won after flagging all mines correctly.就我而言,我正在构建一个扫雷游戏,我想要实现的是在正确标记所有地雷后找出玩家是否真的赢了。 A player wins when setting flags over every cell that contains a mine so you cannot click on it.在每个包含地雷的单元格上设置标志以便您无法点击它时,玩家获胜。 To do so I've created a method called checkIfWin() that does the following:为此,我创建了一个名为 checkIfWin() 的方法,它执行以下操作:

  1. store in an array the board data information (every cell with its properties) and in a counter the number of remaining mines.将棋盘数据信息(每个单元及其属性)存储在一个数组中,并将剩余地雷的数量存储在一个计数器中。 Both are extracted from the component state in two variables called data and minesLeft两者都从名为dataminesLeft 的两个变量中的组件状态中提取
  2. If there are 0 mines remaining (user has flagged as many cells as there are mines in the game), you then compare two arrays ( minesArray containing every mine position in the board and flagsArray containing every flag position)如果剩余地雷为 0(用户标记的单元格数量与游戏中的地雷数量相同),则比较两个数组(包含棋盘中每个地雷位置的minesArray和包含每个标记位置的flagsArray
  3. If the arrays contain the same info (flags are covering every mine), you win.如果数组包含相同的信息(标志覆盖每个地雷),你就赢了。 If the arrays differ, keep playing.如果数组不同,继续播放。

This function is called from two different places这个函数是从两个不同的地方调用的

  1. After a player clicks the last cell that doesn't contain a mine and every tile containing flag has been properly flagged在玩家点击最后一个不包含地雷的单元格并且每个包含标志的图块都被正确标记后
  2. After a player flags a cell and the remaining mines to cover in the board is 0 In the first situation there is no problem it works fine.在玩家标记一个单元格并且棋盘上剩余的地雷为 0 后,在第一种情况下,它可以正常工作。 I check if minesLeft in status is === 0 and if so I call the method checkIfWin() .我检查状态中的minesLeft是否为 === 0,如果是,则调用方法checkIfWin() Since minesLeft only decreases after flagging a cell, not by clicking one, there's no problem here.由于 minesLeft 仅在标记一个单元格后才会减少,而不是通过单击一个单元格,因此这里没有问题。

But after a player flags on a cell, I cannot manage to find a solution to declare if a player has won the game or not.但是在玩家在单元格上标记后,我无法设法找到一个解决方案来声明玩家是否赢得了比赛。 This is my flag() logic:这是我的 flag() 逻辑:

  1. Check if a tile it is covered (it hasn't been clicked on) and if the player didn't win yet (to prevent clicking tiles after the game has ended)检查它是否被覆盖(还没有被点击)以及玩家是否还没有获胜(以防止在游戏结束后点击瓷砖)
  2. If a tile is covered and flagged, unflag it and update counter.如果一个图块被覆盖并被标记,取消标记并更新计数器。
  3. If tile is covered, and it is not flagged then cover it with a flag and decrease the minesLeft counter.如果瓷砖被覆盖,并且没有被标记,则用旗帜覆盖它并减少minesLeft计数器。
  4. When minesLeft is 1 and Player flags a new cell, minesLleft is now 0, so this is when I have to check for a win.minesLeft为 1 并且 Player 标记一个新单元格时, minesLleft 现在为 0,所以这是我必须检查是否获胜的时候。
  5. Now this is where i would like to check if the player did win To do so, I would have to call this.setState() to update the flagsArray with the position of the new flag on the board and later call checkIfWin() -that extracts a value stored inside state- from within this method as well.现在这是我想检查玩家是否赢了要这样做的地方,我必须调用 this.setState() 以使用新标志在棋盘上的位置更新flagsArray ,然后调用 checkIfWin() -that也从该方法中提取存储在 state- 中的值。

This results in the this.setState() call to batch up before executing fully 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.这导致 this.setState() 调用在完全执行之前进行批处理,以便当我尝试比较 checkIfWin() 中是否minesLeft ===0 时,存储在 state 中的值是最后一次标记之前的值 state(改为 1 0),它没有更新,因为 React 的 this.setState() 调用的堆叠逻辑。 Also mentioned on JusticeMba article on medium 媒体上的JusticeMba文章也提到了

React may batch multiple setState() calls into a single update for performance. React 可以将多个 setState() 调用批处理为单个更新以提高性能。

So WHAT I WANT TO KNOW (sorry for the length of the question) is if there is any way that I could do this?所以我想知道的是(对不起,问题的长度)是否有任何方法可以做到这一点? I'm actually binding this flagging method as a right click handler on a component inside render() of the Board.我实际上将此标记方法绑定为 Board 的 render() 内组件上的右键单击处理程序。 Code will follow below.代码将在下面进行。 I don't care about if you know some solution in code or pseudo code.我不在乎你是否知道代码或伪代码中的一些解决方案。 I would take both it in, since I don't seem to find a proper fix for this.我会同时接受它,因为我似乎没有找到合适的解决方法。

Code:代码:

Inside Board.js render() Board.js 内部渲染()

 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>

The rightClickHandler() that manages flagging of Tiles:管理 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() 

    }

Lastly, 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: I'm trying to access the value of a component's property with an outdated state that was supposed to be updated from a previous this.setState() call inside a click handler of a JSX component inside render() TL; DR:我正在尝试访问具有过时状态的组件属性的值,该状态应该从之前的 this.setState() 调用在 render() 内的 JSX 组件的单击处理程序中更新

Sorry for the length, and I hope it is understandable, if not I can edit for clarity.对不起,长度,我希望它是可以理解的,如果不是,我可以编辑清楚。

Assuming I understood it right, you could simply pass checkIfWin as a callback to your setState .假设我理解正确,您可以简单地将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() has a callback, so up there you are just providing the what to run. this.setState() 有一个回调,所以在那里你只是提供要运行的内容。

this.setState(state, callback);

So, up there what's happening there is:所以,那里正在发生的事情是:

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

Also you can write it alternatively:你也可以写成:

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

updateState(){

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

Hope it was understandable.希望这是可以理解的。

Following Felipe Lanza and Nomi G approach and by understanding how react setState callback works, I've come to a working solution that is as follows:遵循Felipe LanzaNomi G 的方法并通过了解react setState 回调的工作原理,我得出了一个可行的解决方案,如下所示:

  1. After a user flags as many tiles as there are mines in the board (minesLeft === 0), I use setState callback with checkIfWin() to check if the player had won在用户标记了与棋盘中地雷一样多的图块后(minesLeft === 0),我使用 setState 回调和 checkIfWin() 来检查玩家是否赢了
  2. I've modified checkIfWin() to update the state in case the game is won, instead of returning a boolean.我修改了 checkIfWin() 以更新状态,以防游戏获胜,而不是返回布尔值。

This is what my code looks like: Inside rightClickHandler():这是我的代码的样子:在 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
}

And this is what my tweaked checkIfWin() looks like now:这就是我调整后的 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!"
            });
        }
    } 
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM