setInterval 触发两次 - React

问题描述 投票:0回答:1

我正在尝试解决 React 中的一个很酷的问题 来自此视频

简而言之,我们需要在商店中有 5 条收银台,以及输入和一个按钮。输入只是一个数字,当我们单击“添加”时,我们基本上将一个拥有 x 数量杂货(x = 输入字段中的数字)的人添加到结账行,其需要处理的杂货总量最少,基本上是一个队列,但我们需要找到杂货总量最少的结账队伍。

每一秒,每个结帐队列中的第一个人都会减少 1,直到达到零,然后我们处理队列中的下一个人。

我尝试在react中解决它并将其分成3个部分:

  • Shop:在 useState 和 checkoutlineToShoppers 中保存“k”条结帐线,checkoutlineToShoppers 是映射/对象,将每个结帐线索引映射到其购物者。
  • 购物者:仅持有大量杂货
  • CheckoutLine:保存索引和数字列表(第一个数字是第一个购物者的金额,第二个数字是第二个购物者的金额,等等)

我不会在这里详细介绍,但主要逻辑发生在

Shop
组件中,这里是完整的,但我将在这里复制一些片段以供上下文使用:

各州:

const [numberOfCheckoutLines, setNumberOfCheckoutLines] = useState<number>(5);
const [checkoutlineToShoppers, setCheckoutlineToShoppers] = useState<CheckoutlineData>(initializeDictionary(numberOfCheckoutLines));

初始化字典的函数,该字典将结帐行的索引映射到空数字数组

function findShopWithMinimumShoppers(checkoutlines: CheckoutlineData) {
    let minShopId: number = 0;
    let minSum = Infinity;
    console.log(checkoutlines)
    for (const shopId in checkoutlines) {
        const shoppersSum = checkoutlines[shopId].shoppers.reduce((acc, curr) => acc + curr, 0);
    
        if (shoppersSum < minSum) {
        minSum = shoppersSum;
        minShopId = Number(shopId);
        }
    }
    
    return minShopId;
    }

找到杂货总量最少的结账线的逻辑。

有问题的

useEffect

useEffect(() => {
        const intervalId = setInterval(() => {
          setCheckoutlineToShoppers(prevCheckoutlines => {
            const updatedCheckoutlines = { ...prevCheckoutlines };
    
            for (const shopId in updatedCheckoutlines) {
              if (updatedCheckoutlines[shopId].shoppers.length > 0) {
                // Decrement the first shopper by 1
                const firstShopper = updatedCheckoutlines[shopId].shoppers[0];
    
                // Check if the first shopper should be removed
                if (firstShopper <= 0) {
                  // Remove the first shopper
                  updatedCheckoutlines[shopId].shoppers.shift();
                } else {
                  // Update the first shopper's value
                  updatedCheckoutlines[shopId].shoppers[0] = firstShopper - 1;
                }
              }
            }
    
            return updatedCheckoutlines;
          });
        }, 1000); // Every second
    
        return () => clearInterval(intervalId); // Clean up the interval when component unmounts
      }, []);

每秒我都会迭代每个结帐行,找到其第一个元素并将其设置为值 -1,但不幸的是,问题是它将每个结帐行中的每个第一个人减少 2 而不是 1...我输入全部 5 个,1 秒后下降到 3,2 秒后全部下降到 1..

enter image description here

这里有什么问题吗?为什么会发生两次?我什至不知道从哪里开始解决这个问题,因为我所做的就是当组件安装时,它只是使用 useEffect 钩子启动一秒的

setInterval
,然后清除它。

提前致谢,如果您确实需要所有源代码,我会尝试找到一种方法来发送所有文件,包括

.css

reactjs typescript react-hooks
1个回答
0
投票

您的间隔回调逻辑中出现了意外的状态突变。

useEffect(() => {
  const intervalId = setInterval(() => {
    setCheckoutlineToShoppers(prevCheckoutlines => {
      const updatedCheckoutlines = { ...prevCheckoutlines };

      for (const shopId in updatedCheckoutlines) {
        if (updatedCheckoutlines[shopId].shoppers.length > 0) {
          // Decrement the first shopper by 1
          const firstShopper = updatedCheckoutlines[shopId].shoppers[0];

          // Check if the first shopper should be removed
          if (firstShopper <= 0) {
            // Remove the first shopper
            updatedCheckoutlines[shopId].shoppers.shift(); // <-- mutation of updatedCheckoutlines[shopId].shoppers array
          } else {
            // Update the first shopper's value
            updatedCheckoutlines[shopId].shoppers[0] = firstShopper - 1; // <-- mutation of updatedCheckoutlines[shopId] object
          }
        }
      }

      return updatedCheckoutlines;
    });
  }, 1000);

  return () => clearInterval(intervalId);
}, []);

prevCheckoutlines
updatedCheckoutlines
的浅拷贝只是通过引用将属性复制到新的外部对象引用中,但所有属性仍然指向当前状态。

所有 React 状态和嵌套状态,必然需要浅复制 然后 正在更新的部分应该被覆盖。

示例:

useEffect(() => {
  const intervalId = setInterval(() => {
    setCheckoutlineToShoppers(prevCheckoutlines => {
      const updatedCheckoutlines = { ...prevCheckoutlines };

      for (const shopId in updatedCheckoutlines) {
        if (updatedCheckoutlines[shopId].shoppers.length > 0) {
          const firstShopper = updatedCheckoutlines[shopId].shoppers[0];

          // Check if the first shopper should be removed
          if (firstShopper <= 0) {
            // Remove the first shopper
            updatedCheckoutlines[shopId] = {
              // Shallow copy shop into new object reference
              ...updatedCheckoutlines[shopId],

              // Use slice instead of shift to create new array reference
              // and keep all but last element
              shoppers: updatedCheckoutlines[shopId].shoppers.slice(0, -1),
            };
          } else {
            // Update the first shopper's value
            updatedCheckoutlines[shopId] = {
              // Shallow copy shop into new object reference
              ...updatedCheckoutlines[shopId],

              // Use map to create new array reference
              shoppers: updatedCheckoutlines[shopId].shoppers.map((shopper, index) => {
                return index === 0 ? shopper - 1 : shopper
              }),
            };
          }
        }
      }

      return updatedCheckoutlines;
    });
  }, 1000);

  return () => clearInterval(intervalId);
}, []);
© www.soinside.com 2019 - 2024. All rights reserved.