【问题标题】:How to use React state variables in setInterval in a handler function如何在处理函数的 setInterval 中使用 React 状态变量
【发布时间】:2024-01-22 11:44:01
【问题描述】:

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

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>
    )
}

【问题讨论】:

    标签: reactjs use-state react-state


    【解决方案1】:

    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>
        )
    }
    

    【讨论】:

    • 工作就像一个魅力,你是我的英雄!哈哈,非常感谢。
    【解决方案2】:

    这里发生的一些事情可能会让你绊倒。

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

    鉴于这一切,IMO 最好的方法是使用 useEffectsetTimeout 实现这种性质的计时器/计数器

    这里有一个快速代码沙盒来演示解决方案: https://codesandbox.io/s/cold-voice-cu49u?file=/src/App.js

    不要为此感到气馁,我已经陷入了这个陷阱,我知道其中的陷阱。

    【讨论】:

      最近更新 更多