为什么onClick会导致循环?

时间:2020-01-15 21:28:48

标签: javascript reactjs

我正在尝试在React中实现Minesweeper,每当玩家单击地雷时,板都会重置并重新渲染,但是当板重置后,玩家最初单击的包含地雷的单元似乎会再次触发onClick 。 另外我还注意到,如果我在撞到地雷后不重置板,而是调用alert()然后返回而不会更改状态,那么游戏就会循环播放,直到发生堆栈溢出为止。

这是在游戏结束后显示警报且不更改状态时状态板组件的外观:

render() {
  let squareGrid = this.state.currentGrid.slice();
  return (
    squareGrid.map((row, y) => { //For each row
        return ( //Create a division
          <div key={y}>
            {
              row.map((state, x) => {//Render a square for each index
                let value = (state.touched) ? state.minedNeighbors :"_";
                return <Square mine={squareGrid[y][x].mine} key={x} disabled={state.touched} val={value}
                  onClick={() => this.handleClick(y, x)}> </Square>

              })}
          </div>
        )
      }
    )
  )
}

handleClick(row, column) {
  // Get copy of grid
  const grid = this.state.currentGrid.slice();
  //If the player clicks a mine, game over.
  if (grid[row][column].mine) {
    //this.resetGame(); //This function does cause a state change

    alert("You have died.");
    return;
  }

  //Non-pure function that mutates grid
  this.revealNeighbors(row, column, grid);

  this.setState({
    currentGrid: grid
  })
}

我的Square组件是一个函数

function Square(props) {
    return (
        <button className={"gameButton"} disabled={props.disabled} onClick={props.onClick}>
            {props.val}
        </button>
    );
}

按原样,一旦玩家单击地雷,该代码将反复显示警报。 如果我取消对handleClickClick中重置游戏的行的注释,则可以正确重置棋盘,但是会显示玩家最后一次单击的单元格,就像玩家在重置棋盘后再次单击它一样。

很多其他困扰我的问题是由于onClick属性包含函数调用而不是函数指针,但据我所知,我不是直接在render中调用函数;我正在提供关闭服务。

编辑: 这是我的董事会组件的完整代码。

class Board extends React.Component {
    constructor(props) {
        super(props);
        let grid = this.createGrid(_size);

        this.state = {
            size: _size,
            currentGrid: grid,
            reset: false
        }
    }

    createGrid(size) {
        const grid = Array(size).fill(null);
        //Fill grid with cell objects
        for (let row = 0; row < size; row++) {
            grid[row] = Array(size).fill(null);
            for (let column = 0; column < size; column++) {
                grid[row][column] = {touched: false, mine: Math.random() < 0.2}
            }
        }

        //Reiterate to determine how many mineNeighbors each cell has
        for (let r = 0; r < size; r++) {
            for (let c = 0; c < size; c++) {
                grid[r][c].minedNeighbors = this.countMineNeighbors(r, c, grid)
            }
        }

        return grid;
    }

    handleClick(row, column) {
        const grid = this.state.currentGrid.slice();

        //If the player clicks a mine, game over.
        if (grid[row][column].mine) {
            //this.resetGame();
            //grid[row][column].touched = true;
            alert("You have died.");
            return;
        }

        //Non-pure function that mutates grid
        this.revealNeighbors(row, column, grid);

        this.setState({
            currentGrid: grid
        })
    }

    //Ensure cell is in bounds
    checkBoundary(row, column) {
        return ([row, column].every(x => 0 <= x && x < this.state.size));
    }


    revealNeighbors(row, column, grid) {
        //Return if out of bounds or already touched
        if (!this.checkBoundary(row, column) || grid[row][column].touched) {
            return;
        }

        //Touch cell
        grid[row][column].touched = true;

        if (grid[row][column].minedNeighbors === 0) {
            //For each possible neighbor, recurse.
            [[1, 0], [-1, 0], [0, 1], [0, -1]]
                .forEach(pos => this.revealNeighbors(row + pos[0], column + pos[1], grid));
        }

    }


    countMineNeighbors(row, column, grid) {
        let size = grid.length;

        //Returns a coordinate pair representing the position of the cell in the direction of the angle, eg, Pi/4 radians -> [1,1]
        let angleToCell = (angle) => [Math.sin, Math.cos]
            .map(func => Math.round(func(angle)))
            .map((val, ind) => val + [row, column][ind]);

        return Array(8)
            .fill(0)
            .map((_, ind) => ind * Math.PI / 4) //Populate array with angles toward each neighbor
            .map(angleToCell)
            .filter(pos => pos.every(x => 0 <= x && x < size))//Remove out of bounds cells
            .filter(pos => grid[pos[0]][pos[1]].mine)//Remove cells that aren't mines
            .length //Return the length of the array as the count
    }

    resetGame() {
        this.setState({
                currentGrid: this.createGrid(this.state.size)
            }
        )
    }

    render() {
        let squareGrid = this.state.currentGrid.slice();
        return (
            squareGrid.map((row, y) => { //For each rows
                    return ( //Create a division
                        <div key={y}>
                            {
                                row.map((state, x) => {//Render a square for each index
                                    let value = (state.touched) ? state.minedNeighbors : "_";
                                    return <Square mine={squareGrid[y][x].mine} key={x} disabled={state.touched} val={value}
                                                   onClick={() => this.handleClick(y, x)}/>

                                })}
                        </div>
                    )
                }
            )
        )
    }
}

3 个答案:

答案 0 :(得分:0)

每当更改状态时,都会调用render方法。

this.setState({
        currentGrid: grid
    })

可能您应该实现一个名为shouldComponentUpdate的方法,以防止发生这种情况。另外,您的分片没有得到解决。我建议您尝试使用异步/等待。

答案 1 :(得分:0)

如果您单击要重新渲染的元素,就会遇到这样的问题。我不确定这是否可以解决您的特定情况下的问题,但是我发现过去有两种对我有用的解决方案。

一种方法是在您的鼠标单击事件中添加一个标记,

if(!mouseDownFlag){
mouseDownFlag = true;
//the rest of your onetime code
}

,然后在mouseupevent上删除该标志

或者,有时使用mousedown事件而不是mouseclick可以更可预测。

希望这些解决方案之一可以帮助您。

答案 2 :(得分:0)

您需要更改传递方式并使用click函数(将传递函数作为道具时,您只希望传递对该函数的引用而不调用它,因此排除了()

 return 
     <Square 
         mine={squareGrid[y][x].mine} 
         key={x} disabled={state.touched} 
         val={value}
         // **** change line below
         onClick={this.handleClick}
     > </Square>

调用时

 <button 
     className={"gameButton"} 
     disabled={props.disabled} 
     // **** change line below
     onClick={() => props.onClick()}>
         {props.val}
</button>