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

时间:2018-08-31 01:28:33

标签: javascript reactjs react-native logic pseudocode

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

就我而言,我正在开发一个扫雷游戏,而我试图实现的目标是找出玩家在正确标记所有地雷之后是否真正获胜。在包含地雷的每个单元格上设置标志时,玩家会获胜,因此您无法单击它。 为此,我创建了一个名为checkIfWin()的方法,该方法执行以下操作:

  1. 以阵列形式存储板数据信息(具有其属性的每个单元),并在计数器中存储剩余地雷的数量。都从组件状态中提取两个变量,分别称为 data minesLeft
  2. 如果还有0个地雷(用户标记的单元格与游戏中的地雷一样多),则可以比较两个数组( minesArray ,其中包含棋盘中的每个地雷位置, flagsArray 包含每个标志位置)
  3. 如果数组包含相同的信息(旗帜覆盖着每个地雷),您将获胜。如果阵列不同,请继续播放。

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

  1. 玩家单击最后一个不包含地雷的单元并且每个包含标志的图块均已正确标记
  2. 玩家标记一个单元格后,棋盘上剩余的地雷为0 在第一种情况下,没有问题,它可以正常工作。我检查状态中的 minesLeft 是否=== 0,如果是,则调用方法 checkIfWin()。因为minesLeft仅在标记一个单元格后才减少,而不是单击一个单元格,所以这里没有问题。

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

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

这导致对batch up before executing fully的this.setState()调用,因此当我尝试比较checkIfWin()中的 minesLeft === 0时,存储在state中的值是值状态在上一次标记之前(从1改为0)之前,它没有更新,因为Reac对此this.setState()调用没有堆积逻辑。在JusticeMba article on medium

中也提到了
  

React可以将多个setState()调用批处理为一个更新,以提高性能。

所以我想知道的(抱歉,问题的长度)是,是否有什么办法可以做到这一点?我实际上将此标记方法绑定为Board的render()内部组件的右键单击处理程序。代码如下。我不在乎您是否知道某些代码或伪代码解决方案。我会把两者都考虑进去,因为我似乎没有找到合适的解决方案。

代码:

Inside 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() 

    }

最后,检查ifWin():

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

    //No need to create a new array, Ill 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:我试图使用过时的状态来添加组件属性的值,该状态应该是通过render()内JSX组件的点击处理程序中的previos this.setState()调用来更新的。 >

很抱歉,篇幅太长了,我希望这是可以理解的。

3 个答案:

答案 0 :(得分:1)

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

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

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

}

答案 1 :(得分:1)

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());
}

希望这是可以理解的。

答案 2 :(得分:0)

按照Felipe LanzaNomi G的方法,通过了解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!"
            });
        }
    } 
}