如何在处理函数的 setInterval 中使用 React 状态变量

How to use React state variables in setInterval in a handler function

我的代码看起来像下面的代码(我删掉了大部分代码以突出问题)。我试图在单击按钮时调用一个调用 setInterval 的函数来增加父组件的状态。不过,记录的只是“0”。 “setPercent”调度函数似乎工作正常 - 在调用它之后我知道“百分比”正在更新为 1,但是当它在下一个间隔返回时,控制台再次记录 0 并且设置再次设置为 1。我假设正在发生的事情是,当创建“clickHandler”函数时,它很难复制“percent”的值而不是引用它 - 我不知道为什么以及如何修复它。非常感谢任何帮助!

const Component = () => {
    const [percent, setPercent] = useState(0)

    const clickHandler = (time) => {
        let timer = setInterval(() => {
            if (percent < 100) {
                console.log('current percent', percent)
                setPercent(percent + 1)
            } else {
                clearInterval(timer)
            }
        }, time / 100)
    }

    return (
        <div>
            <Button onClick={clickHandler} />
        </div>
    )
}

setState的函数形式在这里应该有用。当给定一个函数时,setState 以当前值作为参数调用该函数(而不是通过闭包访问的旧值)。以下应该有效:

const Component = () => {
    const [percent, setPercent] = useState(0);

    const clickHandler = (time) => {
        let timer = setInterval(() => {
            setPercent((current) => {
                if (current < 100) {
                    console.log('current percent', current)
                    return current + 1;
                } else {
                    clearInterval(timer);
                    return current;
                }
            });
        }, time / 100);
    }

    return (
        <div>
            <Button onClick={clickHandler} />
        </div>
    )
}

虽然在 setState 函数中有像 clearInterval 这样的副作用感觉并不理想。这是使用 useEffect 停止计时器的另一种方法,它也强制使用单个计时器(即使按钮被多次单击):

const Component = () => {
    const [percent, setPercent] = useState(0);
    const [timer, setTimer] = useState();

    // Stop running timer when we reach 100%
    useEffect(() => {
        if (percent >= 100 && timer != null) {
            clearInterval(timer);
            setTimer(null);
        }
    }, [percent, timer]);

    const clickHandler = (time) => {
        // Stop any existing timer
        if (timer) {
            clearTimer(timer);
        }
        // Start new timer
        setTimer(setInterval(() => {
            setPercent((current) => current + 1);
        }, time / 100));
    }

    return (
        <div>
            <Button onClick={clickHandler} />
        </div>
    )
}

这里发生的一些事情可能会误导您。

首先,每次更改百分比时,您的组件都会重新呈现。这可能导致有多个间隔 运行。 其次,在每次重新渲染时都会创建一个百分比 const 的新实例,这意味着每个间隔都使用创建间隔时它所持有的值进行操作,这会导致冲突的更新值被发送到 useState。

考虑到这一切,IMO 最好的方法是使用 useEffectsetTimeout

实现这种性质的 timer/counter

这是一个用于演示解决方案的快速代码沙箱: https://codesandbox.io/s/cold-voice-cu49u?file=/src/App.js

不要为此感到气馁,我已经掉进了这个陷阱很多我知道陷阱。