我正在尝试在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>
)
}
)
)
}
}
答案 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>