Как получить доступ к состоянию компонента в функции, которая находится внутри другой функции, которая вызывает this.setState ()? - PullRequest
0 голосов
/ 31 августа 2018

Я создаю свое первое приложение реагирования и столкнулся с такой ситуацией, когда мне приходится дважды вызывать this.setState () в методе. один из этих вызовов также выполняется внутри вложенной функции, вызываемой из метода.

В моем случае я строю игру тральщика, и я пытаюсь добиться того, чтобы выиграть, действительно ли игрок выиграл, правильно отметив все мины. Игрок выигрывает, когда устанавливает флаги над каждой ячейкой, в которой находится мина, поэтому вы не можете нажать на нее. Для этого я создал метод checkIfWin (), который выполняет следующие действия:

  1. хранить в массиве информацию о плате (каждая ячейка со своими свойствами) и в счетчике количество оставшихся мин. Оба извлекаются из состояния компонента в две переменные, называемые data и minesLeft
  2. Если осталось 0 мин (пользователь отметил столько ячеек, сколько мин в игре), то вы сравниваете два массива ( minesArray , содержащий каждую позицию мин на доске и flagsArray содержит каждую позицию флага)
  3. Если массивы содержат одинаковую информацию (флаги покрывают каждую шахту), вы выигрываете. Если массивы отличаются, продолжайте играть.

Эта функция вызывается из двух разных мест

  1. После того, как игрок щелкнул последнюю ячейку, в которой нет мины, и каждая клетка, содержащая флаг, была правильно помечена
  2. После того, как игрок пометил ячейку и оставшиеся мины на доске равны 0 В первой ситуации проблем нет, все отлично работает. Я проверяю, является ли minesLeft в статусе === 0, и если это так, я вызываю метод checkIfWin () . Поскольку minesLeft уменьшается только после пометки ячейки, а не щелчка по ней, здесь нет проблем.

Но после того, как игрок пометил ячейку, я не могу найти решение, чтобы объявить, выиграл игрок или нет. Это логика моего флага ():

  1. Проверьте, накрыт ли тайл (на него не нажимали) и не выиграл ли игрок еще (чтобы не нажать на тайл после окончания игры)
  2. Если плитка закрыта и помечена, разблокируйте ее и обновите счетчик.
  3. Если плитка покрыта, и она не помечена, закройте ее флагом и уменьшите счетчик minesLeft .
  4. Когда minesLeft равен 1, а игрок помечает новую ячейку, minesLleft теперь равен 0, поэтому я должен проверить выигрыш.
  5. Теперь это , где я хотел бы проверить, выиграл ли игрок . Для этого мне нужно было бы вызвать this.setState (), чтобы обновить flagsArray с позицией нового флага на плате и последующего вызова checkIfWin (), который извлекает значение, хранящееся внутри состояния, также из этого метода.

Это приводит к вызову this.setState () для пакетной обработки перед выполнением полностью , поэтому при попытке сравнить, если minesLeft === 0 внутри checkIfWin (), значение хранится в состоянии - это значение состояния, которое было до последнего пометки (1 вместо 0), оно не обновлялось из-за логики стека Reac't для вызовов this.setState (). Также упоминается в статье JusticeMba на среднем

React может объединять несколько вызовов setState () в одно обновление для повышения производительности.

Итак ЧТО Я ХОЧУ ЗНАТЬ (извините за длину вопроса), есть ли способ, которым я мог бы сделать это? Я фактически связываю этот метод пометки как обработчик щелчка правой кнопкой мыши на компоненте внутри 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>

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, 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: я пытаюсь получить доступ к значению свойства компонента с устаревшим состоянием, которое, как предполагалось, было обновлено из предыдущего вызова this.setState () внутри обработчика щелчка компонента JSX внутри render ()

Извините за длину, и я надеюсь, что это понятно, если нет, я могу отредактировать для ясности.

Ответы [ 3 ]

0 голосов
/ 31 августа 2018

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

Надеюсь, это было понятно.

0 голосов
/ 31 августа 2018

Следуя Фелипе Ланце и Номи Г подход и поняв, как реагирует обратный вызов setState , я пришел к рабочему решению, которое выглядит следующим образом:

  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!"
            });
        }
    } 
}
0 голосов
/ 31 августа 2018

Если я правильно понял, вы могли бы просто передать checkIfWin в качестве обратного вызова вашему setState.

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

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

}
...