尝试在 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...
    }
}