javascript - 如何在 React 中有条件地设置状态?
问题描述
我有一个模拟 John Conway 的生命游戏的 React SPA。在初始化/加载时,应用程序需要检查 .gridContainer 元素的高度和宽度,以便它知道要为网格绘制多少行和列(参见参考文献 1)。问题是,网格没有在加载时初始化。但是,如果您单击“清除”按钮两次,由于某些奇怪的原因会初始化。请查看 game-of-life-sage.vercel.app 并单击“清除”两次。- 包括控制台日志。
从控制台记录所有内容,似乎只有在定义 numRows 和 numCols 之后调用 clearGrid 函数时,网格才会初始化,这是有道理的。仅在初始化 numRows 和 numCols 而不创建“有条件地调用 React Hook “useState”错误时,我如何设置网格(下面的参考 2 )?我不能把它放在一个依赖 numRows 和 numCols 的 useEffect 中,因为我得到“不能在回调中调用 React Hook “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 中初始化网格状态,会发生以下情况:
解决方案
正如 Drew 所说,您不能在 useEffect 中使用钩子(必须在每次渲染时调用所有钩子)
您的代码中的问题是您在第一次渲染时grid
使用初始化。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)`}}
>
[...]
)
推荐阅读
- spring - 具有不同服务中的变量的共同路径
- amazon-web-services - 为什么我的 OpsWorks 堆栈的一个实例会跳过我的部署?
- javascript - form.io 在 Javascript 中提交查表组件的搜索值
- reactjs - 使用时刻的 React.JS 中两个日期之间的天数差异
- reactjs - 在 PRN 应用程序中引用 PostgreSQL 查询的最佳方式
- python - Pandas groupy 将特定函数聚合/应用到特定列(np.sum、sum)
- docker - 如何将容器的内容正确绑定到主机文件夹?
- reactjs - 在 React 的选择列表选项中使用 TypeScript 类型?
- javascript - React JS 将点击事件传递给另一个组件
- java - 嵌套异常是 java.lang.NoSuchMethodError: 'long com.google.common.io.ByteStreams.exhaust(java.io.InputStream)']