反应-过滤存储中的对象会导致递归/无限循环

时间:2020-09-19 20:05:47

标签: javascript reactjs use-effect

我在我的React应用程序中使用redux存储来存储诸如费用之类的数据。

现在,我在这些组件之一中使用这些费用来计算要显示的TODO徽章计数

const [badgeValue, setBadgeValue] = useState(0);
const expenses = useSelector(({ expenses }: RootState) => expenses.items);

useEffect(() => {
    const setBadge = async () => {
        const badgeNumber = await calculateRequestBadgeNumber(expenses);
        setBadgeValue(badgeNumber);
    };

    setBadge();

    return () => setBadgeValue(0);
}, [expenses]);

这按预期工作。现在,当我想像这样过滤掉已删除的费用时,我遇到了一个怪异的副作用:

 const expenses = useSelector(({ expenses }: RootState) => 
     expenses.items.filter((expense: Expense) => !expense.deleted)
 );

这导致useEffects的无限循环。为什么会这样?

1 个答案:

答案 0 :(得分:0)

与涉及React钩子的大多数事物一样,所涉及对象的标识(例如,用===测试的对象)在这里很重要。当expenses的身份与上次渲染不同时,将触发您的效果。


选择器A

此钩子返回Redux状态的items属性的expenses属性。如果状态不变,则items属性的身份也不会改变。

const expenses = useSelector(({ expenses }: RootState) => expenses.items);

选择器B

这将返回在items数组上调用filter的结果。 filter方法返回一个全新的数组。

const expenses = useSelector(({ expenses }: RootState) => 
     expenses.items.filter((expense: Expense) => !expense.deleted)
);

您正在观察的是选择器B每次在组件渲染时都返回一个新值,因此效果触发得太频繁了。

现在-useSelector can cache its return value,如果状态未更改,则重用,但如果传递给它的选择器函数的身份保持不变,则仅 。您可以通过在模块作用域(而不是组件)中命名选择器来确保这一点。

const filteredExpenses = ({ expenses }: RootState) =>
  expenses.items.filter((expense: Expense) => !expense.deleted);

const MyComponent = () => {
  const [badgeValue, setBadgeValue] = useState(0);
  const expenses = useSelector(filteredExpenses);
  useEffect(() => {
    const setBadge = async () => {
      const badgeNumber = await calculateRequestBadgeNumber(expenses);
      setBadgeValue(badgeNumber);
    };

    setBadge();

    return () => setBadgeValue(0);
  }, [expenses]);
  return <StuffToRender />;
};