我正试图在React中实现Conway的生命游戏,但每当调用新一代时,它都会冻结。我想这是因为不断地重新渲染DOM会带来太多的开销,但我不知道如何解决这个问题,也无法想出简单地发布整个代码的替代方案,所以我提前为冗长的内容道歉。
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
import styled from "styled-components"
interface TileProps {
bool: boolean
}
const Tile: React.FC<TileProps> = ({bool}) => {
const colour = bool == true ? "#00FF7F" : "#D3D3D3"
return (
<div style = {{backgroundColor: colour}}/>
)
}
interface GridProps {
cells: boolean[][]
}
const StyledGrid = styled.div`
display: grid;
grid-template-columns: repeat(100, 1%);
height: 60vh;
width: 60vw;
margin: auto;
position: relative;
background-color: #E182A8;
`
const Grid: React.FC<GridProps> = ({cells}) => {
return (
<StyledGrid>
{cells.map(row => row.map(el => <Tile bool = {el}/>))}
</StyledGrid>
)
}
const randomBoolean = (): boolean => {
const states = [true, false];
return states[Math.floor(Math.random() * states.length)]
}
const constructCells = (rows: number, columns: number): boolean[][] => {
return constructEmptyMatrix(rows, columns).map(row => row.map(e => randomBoolean()))
}
const constructEmptyMatrix = (rows: number, columns: number): number[][] => {
return [...Array(rows)].fill(0).map(() => [...Array(columns)].fill(0));
}
const App: React.FC = () => {
const columns = 100;
const rows = 100;
const [cells, updateCells] = useState<boolean[][]>(constructCells(rows, columns));
useEffect(() => {
const interval = setInterval(() => {
newGeneration();
}, 1000);
return () => clearInterval(interval);
}, []);
const isRowInGrid = (i: number): boolean => 0 <= i && i <= rows - 1
const isColInGrid = (j : number): boolean => 0 <= j && j <= columns -1
const isCellInGrid = (i: number, j: number): boolean => {
return isRowInGrid(i) && isColInGrid(j)
}
const numberOfLiveCellNeighbours = (i: number, j: number): number => {
const neighbours = [
[i - 1, j], [i, j + 1], [i - 1, j + 1], [i - 1, j + 1],
[i + 1, j], [i, j - 1], [i + 1, j - 1], [i + 1, j + 1]
]
const neighboursInGrid = neighbours.filter(neighbour => isCellInGrid(neighbour[0], neighbour[1]))
const liveNeighbours = neighboursInGrid.filter(x => {
const i = x[0]
const j = x[1]
return cells[i][j] == true
})
return liveNeighbours.length;
}
const updateCellAtIndex = (i: number, j: number, bool: boolean) => {
updateCells(oldCells => {
oldCells = [...oldCells]
oldCells[i][j] = bool;
return oldCells;
})
}
const newGeneration = (): void => {
cells.map((row, i) => row.map((_, j) => {
const neighbours = numberOfLiveCellNeighbours(i, j);
if (cells[i][j] == true){
if (neighbours < 2){
updateCellAtIndex(i, j, false);
} else if (neighbours <= 3){
updateCellAtIndex(i, j, true);
}
else {
updateCellAtIndex(i, j, false);
}
} else {
if (neighbours === 3){
updateCellAtIndex(i, j, true);
}
}
}))
}
return (
<div>
<Grid cells = {cells}/>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
应用程序冻结是因为React不会批量处理您的单个状态更新。有关这方面的更多信息可以在这个答案中找到
这里有两个选项。
- 使用
ReactDOM.unstable_batchedUpdates
:
这可以通过单行更改来完成,但请注意,该方法不是公共API 的一部分
useEffect(() => {
const interval = setInterval(() => {
// wrap generation function into batched updates
ReactDOM.unstable_batchedUpdates(() => newGeneration())
}, 1000);
return () => clearInterval(interval);
}, []);
- 在一次操作中更新所有状态
您可以重构代码,只设置一次更新的单元格。此选项不使用任何不稳定的方法
useEffect(() => {
const interval = setInterval(() => {
// `newGeneration` function needs to be refactored to remove all `updateCells` calls. It should update the input array and return the result
const newCells = newGeneration(oldCells);
// there will be only one call to React on each interval
updateCells(newCells);
}, 1000);
return () => clearInterval(interval);
}, []);