我有一个反应SPA模拟约翰康威的生命游戏。在初始化/加载时,应用程序需要检查.gridContainer元素的高度和宽度,以便它知道要为网格绘制多少行和列(参见参考文献1)。问题是,电网在加载时并没有初始化。但是,由于一些奇怪的原因,如果您单击“清除”按钮两次,就会初始化。请查看生命游戏-sage.vercel.app并单击“清除”两次。-包括控制台日志。
从控制台记录所有信息,似乎只有在定义了clearGrid和numCols之后,网格才会初始化,这是有意义的。如何只有在初始化了setGrid和numCols而没有创建一个“React”useState才被称为“有条件的错误”之后,才能进行numRows (参考文献2)?我不能把它放在依赖于numRows和numCols的numRows和numCols中,因为我得到了"React "useState“不能在回调中调用”。
function App() {
let width;
let height;
let numRows;
let numCols;
// REF 1
if (document.readyState === 'complete') {
let getGridSize = document.querySelector('.gridcontainer');
width = getGridSize.offsetWidth
height = getGridSize.offsetHeight
let useableCols = (width / gridSize);
let useableRows = (height / gridSize);
numRows = Math.round(useableRows);
numCols = Math.round(useableCols);
}
if (document.readyState === 'complete') {
console.log("numRows: ",numRows);
console.log("numCols: ",numCols);
}
const clearGrid = () => {
console.log("clearGrid called");
const rows = [];
for (let i = 0; i < numRows; i++) {
rows.push(Array.from(Array(numCols), () => 0))
}
return rows
}
// REF 2
const [grid, setGrid] = useState(() => {
console.log("init grid state");
return clearGrid()
})
const [interval, setInterval] = useState(50);
const intervalRef = useRef(interval);
intervalRef.current=(interval*10);
const [running, setRunning] = useState(false)
const runningRef = useRef(running)
runningRef.current = running;
const randomiseGrid = () => {
const rows = [];
for (let i = 0; i < numRows; i++) {
rows.push(Array.from(Array(numCols), () => Math.random() > 0.9 ? 1 : 0))
}
setGrid(rows);
}
const runSimulation = useCallback(() => {
if (!runningRef.current) {
return;
}
setGrid((g) => {
return produce(g, gridCopy => {
for (let i = 0; i < numRows; i++) {
for (let k = 0; k < numCols; k++) {
let neighbours = 0;
operations.forEach(([x,y]) => {
const newI = i + x;
const newK = k + y;
// check if out of bounds
if (newI >= 0 && newI < numRows && newK >= 0 && newK < numCols) {
neighbours += g[newI][newK]
}
})
// apply rules
if (neighbours < 2 || neighbours > 3) {
gridCopy[i][k] = 0;
} else if (g[i][k] === 0 && neighbours === 3) {
gridCopy[i][k] = 1;
}
}
}
})
})
setTimeout(runSimulation, intervalRef.current)
},[numCols, numRows])
useEffect(() => {
console.log("grid: ",grid)
},[])
return (
<>
<div className="flex flex-col w-screen h-screen bg-white">
<div className="flex items-center justify-end h-16 bg-white px-11">
<div className="flex items-center justify-center text-sm font-bold tracking-tight text-right text-black uppercase w-72">george conway's game of life</div>
</div>
<main className="flex flex-row w-full h-full p-11">
<div
className="w-full h-full bg-gray-100 rounded gridcontainer"
style={{display: "grid",gridTemplateColumns: `repeat(${numCols}, 20px)`}}
>
{grid.map((rows, i) => rows.map((col, k) =>
<div
key={`${i}-${k}`}
onClick={() => {
const newGrid = produce(grid, gridCopy => {
gridCopy[i][k] = grid[i][k] ? 0 : 1;
})
setGrid(newGrid)
}}
className={`w-5 h-5 border border-gray-300 ${grid[i][k] ? 'bg-black' : undefined}`}/>
))}
</div>
<div className="flex flex-col items-center h-full space-y-12 w-72 ml-11">
<div>
<h3 className="text-sm text-center text-gray-700 uppercase">rules (how things evolve)</h3>
<ul className="flex flex-col mt-4 space-y-4">
<li className="text-sm font-light text-center text-gray-600">Any live cell with fewer than two or more than three live neighbours dies</li>
<li className="text-sm font-light text-center text-gray-600">Any dead cell with exactly three live neighbours becomes a live cell</li>
</ul>
</div>
<button
onClick={() => {setRunning(!running)
if (!running) {
runningRef.current = true;
runSimulation();
}
}}
className="flex items-center justify-center w-48 h-12 font-medium text-white bg-gray-500 rounded focus:outline-none">
{running ? 'stop' : 'evolve'}
</button>
<button
onClick={randomiseGrid}
className="flex items-center justify-center w-48 h-12 font-medium text-white bg-gray-500 rounded focus:outline-none">
randomise
</button>
<button
onClick={() => {setGrid(clearGrid())}}
className="flex items-center justify-center w-48 h-12 font-medium text-white bg-gray-500 rounded focus:outline-none">
clear
</button>
<div className="w-72">
<div className="mb-10 text-sm font-light text-center text-gray-600 uppercase">set interval</div>
<Slider
min={1}
max={100}
step={1}
color="gray"
defaultValue={50}
labelAlwaysOn
value={interval}
onChange={setInterval}
/>
</div>
<p className="text-sm font-light text-center text-gray-600">
The Game of Life is a cellular automaton devised by Dr John Conway in 1970. The game is a zero-player game, meaning that its evolution is determined by its initial state. One interacts with the Game of Life by creating an initial configuration and observing how it evolves.
</p>
</div>
</main>
</div>
</>
);
}任何帮助都非常感谢。
编辑:
如果我在onMount useEffect中初始化网格状态,会发生什么情况:

发布于 2021-08-01 09:23:11
正如Drew所说,您不能在useEffect中使用钩子(必须在每次呈现时调用所有钩子)
代码中的问题是在第一次呈现时使用clearGrid()初始化clearGrid()。但在该函数中使用的numRows仍未定义。
您可以使用useRef和useEffect来降低网格的级别。
const [grid, setGrid] = useState();
const gridcontainer = useRef(null);
width = gridcontainer.current?.offsetWidth // consider that might be undefined
height = gridcontainer.current?.offsetHeight
let useableCols = (width / gridSize);
let useableRows = (height / gridSize);
numRows = Math.round(useableRows);
numCols = Math.round(useableCols);
const clearGrid = () => {/*...*/}
useEffect(()={
if(gridcontainer.current){
// gridcontainer is loaded
setGrid(clearGrid())
}
}, [gridcontainer.current])
return (
[...]
<div
ref={gridcontainer} // pass the reference to useRef
className="w-full h-full bg-gray-100 rounded gridcontainer"
style={{display: "grid",gridTemplateColumns: `repeat(${numCols}, 20px)`}}
>
[...]
)https://stackoverflow.com/questions/68607901
复制相似问题