首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >setState如何有条件地作出反应?

setState如何有条件地作出反应?
EN

Stack Overflow用户
提问于 2021-08-01 06:08:41
回答 1查看 852关注 0票数 2

我有一个反应SPA模拟约翰康威的生命游戏。在初始化/加载时,应用程序需要检查.gridContainer元素的高度和宽度,以便它知道要为网格绘制多少行和列(参见参考文献1)。问题是,电网在加载时并没有初始化。但是,由于一些奇怪的原因,如果您单击“清除”按钮两次,就会初始化。请查看生命游戏-sage.vercel.app并单击“清除”两次。-包括控制台日志。

从控制台记录所有信息,似乎只有在定义了clearGrid和numCols之后,网格才会初始化,这是有意义的。如何只有在初始化了setGrid和numCols而没有创建一个“React”useState才被称为“有条件的错误”之后,才能进行numRows (参考文献2)?我不能把它放在依赖于numRows和numCols的numRows和numCols中,因为我得到了"React "useState“不能在回调中调用”。

代码语言:javascript
复制
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中初始化网格状态,会发生什么情况:

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-08-01 09:23:11

正如Drew所说,您不能在useEffect中使用钩子(必须在每次呈现时调用所有钩子)

代码中的问题是在第一次呈现时使用clearGrid()初始化clearGrid()。但在该函数中使用的numRows仍未定义。

您可以使用useRef和useEffect来降低网格的级别。

代码语言:javascript
复制
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)`}}
    >
    [...]
)
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68607901

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档