You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript中growthCount变量计数超出预期问题求助

问题

我用growthCount变量统计新生成细胞的数量,细胞“分裂”时growthCount自增1,随后通过setState将值存入growthCounts状态变量。该操作在setInterval中按指定间隔重复执行,每次执行时growthCount会重置为0。但持续运行时,growthCount数值经常远超预期——比如模拟中4个细胞分裂本应生成4个新细胞,但growthCount却显示为8。尝试用useRef替代let变量,问题仍存在;但每次分裂后暂停模拟再继续,growthCount就能输出预期值。

相关代码如下:

import "./App.css";
import { useEffect, useState, useRef } from "react";
import PetriDish from "./components/PetriDish";
import Controls from "./components/Controls";
import Graph from "./components/Graph";

function App() {
    const defaultGridSize: number[] = [20, 20];

    // Petri dish vars
    const [gridSize, setGridSize] = useState<number[]>(defaultGridSize);
    const [cells, setCells] = useState(
        Array(defaultGridSize[0]).fill(Array(defaultGridSize[1]).fill(false))
    );
    const [timeInterval, setTimeInterval] = useState<number>(1);
    const [running, setRunning] = useState<boolean>(false);
    const intervalRef = useRef<number>(-1);

    // Graph vars
    const [growthCounts, setGrowthCounts] = useState<number[]>([0]);

    const split = (): void => {
        let growthCount: number = 0;
        setCells((prevCells) => {
            const newCells = prevCells.map((row) => row.slice());
            // Loop through each cell
            for (let i = 0; i < prevCells.length; i++) {
                for (let j = 0; j < prevCells[i].length; j++) {
                    if (prevCells[i][j]) {
                        // Determine empty cells next to existing bacteria
                        let empty = [];
                        // Left
                        if (j > 0 && !newCells[i][j - 1]) {
                            empty.push([i, j - 1]);
                        }
                        // Right
                        if (
                            j < prevCells[i].length - 1 &&
                            !newCells[i][j + 1]
                        ) {
                            empty.push([i, j + 1]);
                        }
                        // Up
                        if (i > 0 && !newCells[i - 1][j]) {
                            empty.push([i - 1, j]);
                        }
                        // Down
                        if (i < prevCells.length - 1 && !newCells[i + 1][j]) {
                            empty.push([i + 1, j]);
                        }

                        // Split bacteria into an empty cell
                        if (empty.length > 0) {
                            const [a, b] = 
                                empty[Math.floor(Math.random() * empty.length)];
                            newCells[a][b] = true;
                            growthCount++;
                        }
                    }
                }
            }
            return newCells;
        });

        // Write cell count after split to graph
        setGrowthCounts((prevGrowthCount) => {
            return [...prevGrowthCount, growthCount];
        });
    };

    const startSim = (): number => {
        return setInterval(split, timeInterval * 1000);
    };

    useEffect(() => {
        if (running) {
            // Error check input fields
            if (Number.isNaN(timeInterval) || timeInterval < 1) {
                setRunning(false);
                alert("Time interval must be at least 1");
            } else if (
                Number.isNaN(gridSize[0]) ||
                gridSize[0] < 1 ||
                Number.isNaN(gridSize[1]) ||
                gridSize[1] < 1
            ) {
                setRunning(false);
                alert("Grid size must be at least 1");
            } else {
                intervalRef.current = startSim();
            }
        } else {
            // Run simulation
            if (intervalRef.current) {
                clearInterval(intervalRef.current);
            }
        }
    }, [running]);

    return (
        <main>
            <PetriDish cells={cells} setCells={setCells} />
            <Controls
                gridSize={gridSize}
                setGridSize={setGridSize}
                setCells={setCells}
                setTimeInterval={setTimeInterval}
                running={running}
                setRunning={setRunning}
                setGrowthCounts={setGrowthCounts}
            />
            <Graph
                timeInterval={timeInterval}
                coords={growthCounts}
                gridSize={gridSize}
            />
        </main>
    );
}

export default App;
原因分析

问题出在split函数的逻辑里:在setCells的更新回调中,你遍历prevCells的同时修改了newCells,而后续循环会把本次分裂刚生成的新细胞也当成已存在的细胞,触发再次分裂并计数。

比如当4个原始细胞分裂出4个新细胞后,这4个新细胞在同一次循环遍历中会被检测为true,进而再次寻找空细胞分裂,导致growthCount被额外累加,最终得到8。而暂停再继续时,下一次split是基于上一轮更新后的cells状态执行的,新细胞不会在同一次循环里被重复处理,所以数值正常。

解决方案

核心思路是:只针对原始状态(prevCells)中的细胞执行分裂逻辑,避免同一次循环里处理刚生成的新细胞。可以先收集所有需要分裂的原始细胞位置,再统一处理它们的分裂操作,这样就不会在遍历过程中处理新生成的细胞。

修改后的split函数代码如下:

const split = (): void => {
    let growthCount: number = 0;
    setCells((prevCells) => {
        const newCells = prevCells.map((row) => row.slice());
        // 先收集所有原始存在的细胞位置
        const existingCells: [number, number][] = [];
        for (let i = 0; i < prevCells.length; i++) {
            for (let j = 0; j < prevCells[i].length; j++) {
                if (prevCells[i][j]) {
                    existingCells.push([i, j]);
                }
            }
        }

        // 只处理原始存在的细胞,避免同轮处理新生成的细胞
        for (const [i, j] of existingCells) {
            // 寻找当前细胞周围的空细胞
            let empty: [number, number][] = [];
            // Left
            if (j > 0 && !newCells[i][j - 1]) {
                empty.push([i, j - 1]);
            }
            // Right
            if (j < prevCells[i].length - 1 && !newCells[i][j + 1]) {
                empty.push([i, j + 1]);
            }
            // Up
            if (i > 0 && !newCells[i - 1][j]) {
                empty.push([i - 1, j]);
            }
            // Down
            if (i < prevCells.length - 1 && !newCells[i + 1][j]) {
                empty.push([i + 1, j]);
            }

            // 分裂到空细胞
            if (empty.length > 0) {
                const [a, b] = empty[Math.floor(Math.random() * empty.length)];
                newCells[a][b] = true;
                growthCount++;
            }
        }
        return newCells;
    });

    setGrowthCounts((prevGrowthCount) => {
        return [...prevGrowthCount, growthCount];
    });
};

这样修改后,每次split只会基于触发该次分裂时的原始细胞状态进行处理,同一次循环里不会处理刚生成的新细胞,growthCount就能准确统计单次分裂生成的新细胞数量。

内容的提问来源于stack exchange,提问作者Doma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 23:16:02