【问题标题】:React setState within setInterval在 setInterval 中反应 setState
【发布时间】:2021-06-02 02:41:47
【问题描述】:

我有一个组件,其中存储了一个对象数组,例如:

const [items, setItems] = React.useState([]);

我可以通过点击一个按钮向这个数组中添加一些元素:

const addItem = () => {
  setItems(items => [ ...items, { value: 'foo', color: 'yellow' }]);
};

现在,我想让列表中的每个项目每 500 毫秒从黄色闪烁到红色,持续 5 秒。所以我创建了一个通用的计时器函数来帮助我,例如:

const timer = (timeout: number, interval: number, callback: (n: number) => void) => {
  const start = new Date().getTime();
  let n = 0;
  const _timer = setInterval(() => {
    if ((new Date().getTime() - start) >= timeout) {
      clearInterval(_timer);
    } else {
      callback(++n);
    }
  }, interval);
};

我通过在我的组件中执行以下操作来触发该计时器:

React.useEffect(() => {
  // I also store the previous state, so that I trigger a timer only when new items
  // are added to my array. Yeah not ideal...
  if (items.length > previousItems.length) {
     const lastItem = items.length - 1;
     timer(5000, 500, (n: number) => {
       const updatedItems = [ ...items ];
       updatedItems[index].color = n % 2 == 0 ? 'yellow' : 'red';
       setItems(items => updatedItems);
     });
  }
});

这种有缺陷的方法可以正常工作,以防我只将项目逐个添加到列表中(例如,计时器在添加新项目之前完成)。但是同时添加多个item的时候就彻底坏了:第一个定时器已经创建并没有考虑到后面添加的所有item。 我知道这种方法根本不理想,但是如果您有更好的方法来实现我想要的,我将不胜感激。谢谢。

【问题讨论】:

    标签: reactjs typescript setinterval use-state


    【解决方案1】:

    不要使用旧的 items,而是使用你的 setter 回调传递的那个,请参阅 *** 评论:

    React.useEffect(() => {
        if (items.length > previousItems.length) {
            const lastItem = items.length - 1;
            timer(5000, 500, (n: number) => {
                // *** Move the entire update into the callback
                setItems(items => {
                    const updatedItems = [ ...items ]; // <=== Now this is using the up-to-date `items`
                    updatedItems[index].color = n % 2 == 0 ? 'yellow' : 'red';
                    return [updatedItems];
                });
            });
        }
    });
    

    您还需要确保在组件卸载或重新渲染时取消间隔计时器,因为您的代码在重新渲染时启动了一个 new 计时器(如果长度检查通过)。这些将很快叠加起来进行多次重叠更新。

    您可以让timer 返回一个cancel 函数:

    const timer = (timeout: number, interval: number, callback: (n: number) => void) => {
        const start = new Date().getTime();
        let n = 0;
        const _timer = setInterval(() => {
            if ((new Date().getTime() - start) >= timeout) {
                clearInterval(_timer);
            } else {
                callback(++n);
            }
        }, interval);
        return () => clearInterval(_timer); // ***
    };
    

    然后将其用作useEffect 清理回调:

    React.useEffect(() => {
        if (items.length <= previousItems.length) {
            return;
        }
        const lastItem = items.length - 1;
        const cancel = timer(5000, 500, (n: number) => {
    //  ^^^^^^^^^^^^
            // *** Move the entire update into the callback
            setItems(items => {
                const updatedItems = [ ...items ]; // <=== Now this is using the up-to-date `items`
                updatedItems[index].color = n % 2 == 0 ? 'yellow' : 'red';
                return [updatedItems];
            });
        });
        return cancel; // ***
    });
    

    【讨论】:

    • 感谢您的回答!现在效果好多了
    猜你喜欢
    • 1970-01-01
    • 2021-10-10
    • 2022-08-05
    • 1970-01-01
    • 2022-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-20
    相关资源
    最近更新 更多