带有 useSelector 的 useEffect 挂钩的无限循环

Infinite loop with useEffect hook with useSelector

我的 React Native 应用程序中有一个规范化的 Redux 存储。

我的reducer的结构是:

{
  byId: {},
  allIds: []
}

在我的组件中,我使用 useSelector 挂钩获取 Redux 状态的切片:

const categories = useSelector((state: AppState) =>
  state.products.allIds.map((id) => state.categories.byId[id.toString()])
);

useSelector 中的逻辑只是将 byId 对象转换为数组。

当我将 categories 数组设置为依赖项时发生无限循环:

const [values, setValues] = useState<any[]>([]);

useEffect(() => {
  setValues([{ id: 1 }]);
  console.log("logging");
}, [categories]);

不确定是什么问题。我相信这是将对象转换为数组的 useSelector 逻辑。

编辑:

完整组件代码:

// React
import React, { useEffect, useState } from "react";

// React redux
import { useSelector } from "react-redux";
import { AppState } from "@reducers/rootReducer";

// Logic
import ProductsScreenLogic from "./ProductsScreen.logic";

// Common components
import ScreenView from "@common/screen/Screen.view";

// Components
import NewProductModalView from "@components/products/new-product-modal/NewProductModal.view";
import ProductsTabsView from "@components/products/products-tabs/ProductsTabs.view";
import ProductListView from "@components/products/products-list/ProductList.view";
import CategoryListView from "@components/products/category-list/CategoryList.view";

const ProductsScreenView: React.FC = () => {
  const { displayProductList, setDisplayProductList, products } =
    ProductsScreenLogic();


  // Makes the categories ById object into an array of categories
  const categories = useSelector((state: AppState) => state.categories.allIds.map((id) => state.categories.byId[id.toString()])
  );


  const [values, setValues] = useState<any[]>([]);

  useEffect(() => {
    setValues([{ id: 1 }]);
    console.log("logging");
  }, [categories]);

  return (
    <>
      <NewProductModalView />
      <ScreenView></ScreenView>
    </>
  );
};

export default ProductsScreenView;

在 useEffect 中更新状态,导致渲染,每次渲染调用 useSelector return useEffect 的新数组,导致更新状态。要修复,您可以从 useEffect 依赖项数组

中删除类别

问题是您的 selector 总是 return 是一个新引用(因为调用了 map)。您可以改用 createSelector 来记忆它,并且当 allIdsbyId 中的某些内容发生变化时仅 return 一个新参考:

const selectAllCategories = createSelector(
    (state: AppState) => state.categories.allIds,
    (state: AppState) => state.categories.byId,
    (categoriesIds, categoriesById) => categoriesIds.map((id) => categoriesById[id.toString()])
);

但理想情况下,您应该避免这样的 selector 遍历整个 byId 对象,因为它会抵消拥有规范化状态的好处。你应该有一个只有 selects state.categories.allIds 的父组件,然后将 id 作为 props 传递给子组件,每个子组件都会 select 它自己的 state.categories.byId[id]。这样,如果类别发生变化,只有相应的子组件会重新渲染,而不是让父组件和所有子组件都重新渲染。