尝试在 React 中制作 2048 游戏。状态中的数据在不应该发生的情况下发生了突变
Try to make 2048 game in React. Data in the state is mutated when it should not have been
这是我的代码库https://github.com/540376482yzb/2048game_react
这是我的现场演示https://objective-fermat-0343a3.netlify.com/
目前游戏状态只有左右走
然而,当没有移动时,它会在按下左箭头键时生成新数字。已确定的问题是 this.state.gridData
在我 运行 this.diffGrid(grid)
之前翻转,因此它将始终 return 正确并添加新号码。
这个问题的嫌疑人是:
// issue ====> flipGrid function is mutating this.state.gridData
flipGrid(grid) {
return grid.map(row => row.reverse())
}
或
//slide left
if (e.keyCode === 37) {
//issue ===> state is flipped on left arrow key pressed
copyGrid = this.flipGrid(copyGrid).map(row => this.slideAndCombine(row))
copyGrid = this.flipGrid(copyGrid)
}
谁能告诉我我哪里做错了导致状态突变?
import React from 'react'
import './App.css'
import Grid from './Grid'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
gridData: [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
}
}
initGame() {
let grid = [...this.state.gridData]
grid = this.addNumber(grid)
grid = this.addNumber(grid)
this.setState({
gridData: grid
})
}
addNumber(grid) {
const availableSpot = []
grid.map((rowData, x) =>
rowData.map((data, y) => {
if (!data) availableSpot.push({ x, y })
})
)
const randomSpot = availableSpot[Math.floor(Math.random() * availableSpot.length)]
grid[randomSpot.x][randomSpot.y] = Math.random() < 0.2 ? 4 : 2
return grid
}
slide(row) {
const newRow = row.filter(data => data)
const zerosArr = Array(4 - newRow.length).fill(0)
return [...zerosArr, ...newRow]
}
combine(row) {
let a, b
for (let i = 3; i > 0; i--) {
a = row[i]
b = row[i - 1]
if (a === b) {
row[i] = a + b
row[i - 1] = 0
}
}
return row
}
slideAndCombine(row) {
row = this.slide(row)
row = this.combine(row)
return row
}
diffGrid(grid) {
let isDiff = false
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid.length; j++) {
if (grid[i][j] != this.state.gridData[i][j]) {
isDiff = true
}
}
}
return isDiff
}
// issue ====> flipGrid function is mutating this.state.gridData
flipGrid(grid) {
return grid.map(row => row.reverse())
}
componentDidMount() {
this.initGame()
let copyGrid = [...this.state.gridData]
window.addEventListener('keyup', e => {
if (e.keyCode === 37 || 38 || 39 || 40) {
//slide right
if (e.keyCode === 39) {
copyGrid = copyGrid.map(row => this.slideAndCombine(row))
}
//slide left
if (e.keyCode === 37) {
//issue ===> state is flipped on left arrow key pressed
copyGrid = this.flipGrid(copyGrid).map(row => this.slideAndCombine(row))
copyGrid = this.flipGrid(copyGrid)
}
// Line 89 issue==>>>>>> gridData in the state
console.table(this.state.gridData)
// diffGrid compares copyGrid with this.state.gridData
if (this.diffGrid(copyGrid)) {
copyGrid = this.addNumber(copyGrid)
//deepCopy of gridData
console.table(copyGrid)
this.setState({
gridData: copyGrid
})
}
}
})
}
render() {
// Line 103 ===>>>> gridData in the state
console.table(this.state.gridData)
return (
<div className="App">
<main className="centerGrid" id="game">
<Grid gridData={this.state.gridData} />
</main>
</div>
)
}
}
export default App
row.reverse() 将 return 反转数组,但它 也会 改变行数组。查看 reverse 的 MDN 文档。因此,由于 map 在迭代元素之前不会复制元素,因此您的 flipGrid 函数将改变传递给它的网格。
为确保您不会改变原始网格,您可以执行以下操作:
flipGrid(grid) {
const gridCopy = grid.slice()
return gridCopy.map(row => row.reverse())
}
或者更简洁一点:
flipGrid(grid) {
return grid.slice().map(row => row.reverse())
}
或者如果您想使用展开运算符:
flipGrid(grid) {
return [...grid].map(row => row.reverse())
}
至于为什么this.state.gridData会变异:数组展开实际上是一个浅拷贝,所以gridCopy仍然引用this.state.gridData的行数组,因此你还在变异this.state.gridData 当你使用 flipGrid 进行变异时。如果你完全停止变异,这没关系,但最好在事件处理程序的开头进行深度复制,而不是在 componentDidMount 中,这样一开始就不会发生这种情况。
您可以使用 lodash 的 cloneDeep 函数或映射到 gridCopy 和浅拷贝(使用扩展运算符或切片)
window.addEventListener('keyup', e => {
if (e.keyCode === 37 || 38 || 39 || 40) {
let gridCopy = this.state.gridData.map((row) => row.slice())
etc...
}
}
这是我的代码库https://github.com/540376482yzb/2048game_react
这是我的现场演示https://objective-fermat-0343a3.netlify.com/
目前游戏状态只有左右走
然而,当没有移动时,它会在按下左箭头键时生成新数字。已确定的问题是 this.state.gridData
在我 运行 this.diffGrid(grid)
之前翻转,因此它将始终 return 正确并添加新号码。
这个问题的嫌疑人是:
// issue ====> flipGrid function is mutating this.state.gridData
flipGrid(grid) {
return grid.map(row => row.reverse())
}
或
//slide left
if (e.keyCode === 37) {
//issue ===> state is flipped on left arrow key pressed
copyGrid = this.flipGrid(copyGrid).map(row => this.slideAndCombine(row))
copyGrid = this.flipGrid(copyGrid)
}
谁能告诉我我哪里做错了导致状态突变?
import React from 'react'
import './App.css'
import Grid from './Grid'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
gridData: [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
}
}
initGame() {
let grid = [...this.state.gridData]
grid = this.addNumber(grid)
grid = this.addNumber(grid)
this.setState({
gridData: grid
})
}
addNumber(grid) {
const availableSpot = []
grid.map((rowData, x) =>
rowData.map((data, y) => {
if (!data) availableSpot.push({ x, y })
})
)
const randomSpot = availableSpot[Math.floor(Math.random() * availableSpot.length)]
grid[randomSpot.x][randomSpot.y] = Math.random() < 0.2 ? 4 : 2
return grid
}
slide(row) {
const newRow = row.filter(data => data)
const zerosArr = Array(4 - newRow.length).fill(0)
return [...zerosArr, ...newRow]
}
combine(row) {
let a, b
for (let i = 3; i > 0; i--) {
a = row[i]
b = row[i - 1]
if (a === b) {
row[i] = a + b
row[i - 1] = 0
}
}
return row
}
slideAndCombine(row) {
row = this.slide(row)
row = this.combine(row)
return row
}
diffGrid(grid) {
let isDiff = false
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid.length; j++) {
if (grid[i][j] != this.state.gridData[i][j]) {
isDiff = true
}
}
}
return isDiff
}
// issue ====> flipGrid function is mutating this.state.gridData
flipGrid(grid) {
return grid.map(row => row.reverse())
}
componentDidMount() {
this.initGame()
let copyGrid = [...this.state.gridData]
window.addEventListener('keyup', e => {
if (e.keyCode === 37 || 38 || 39 || 40) {
//slide right
if (e.keyCode === 39) {
copyGrid = copyGrid.map(row => this.slideAndCombine(row))
}
//slide left
if (e.keyCode === 37) {
//issue ===> state is flipped on left arrow key pressed
copyGrid = this.flipGrid(copyGrid).map(row => this.slideAndCombine(row))
copyGrid = this.flipGrid(copyGrid)
}
// Line 89 issue==>>>>>> gridData in the state
console.table(this.state.gridData)
// diffGrid compares copyGrid with this.state.gridData
if (this.diffGrid(copyGrid)) {
copyGrid = this.addNumber(copyGrid)
//deepCopy of gridData
console.table(copyGrid)
this.setState({
gridData: copyGrid
})
}
}
})
}
render() {
// Line 103 ===>>>> gridData in the state
console.table(this.state.gridData)
return (
<div className="App">
<main className="centerGrid" id="game">
<Grid gridData={this.state.gridData} />
</main>
</div>
)
}
}
export default App
row.reverse() 将 return 反转数组,但它 也会 改变行数组。查看 reverse 的 MDN 文档。因此,由于 map 在迭代元素之前不会复制元素,因此您的 flipGrid 函数将改变传递给它的网格。
为确保您不会改变原始网格,您可以执行以下操作:
flipGrid(grid) {
const gridCopy = grid.slice()
return gridCopy.map(row => row.reverse())
}
或者更简洁一点:
flipGrid(grid) {
return grid.slice().map(row => row.reverse())
}
或者如果您想使用展开运算符:
flipGrid(grid) {
return [...grid].map(row => row.reverse())
}
至于为什么this.state.gridData会变异:数组展开实际上是一个浅拷贝,所以gridCopy仍然引用this.state.gridData的行数组,因此你还在变异this.state.gridData 当你使用 flipGrid 进行变异时。如果你完全停止变异,这没关系,但最好在事件处理程序的开头进行深度复制,而不是在 componentDidMount 中,这样一开始就不会发生这种情况。
您可以使用 lodash 的 cloneDeep 函数或映射到 gridCopy 和浅拷贝(使用扩展运算符或切片)
window.addEventListener('keyup', e => {
if (e.keyCode === 37 || 38 || 39 || 40) {
let gridCopy = this.state.gridData.map((row) => row.slice())
etc...
}
}