带有 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
来记忆它,并且当 allIds
或 byId
中的某些内容发生变化时仅 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]
。这样,如果类别发生变化,只有相应的子组件会重新渲染,而不是让父组件和所有子组件都重新渲染。
我的 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
来记忆它,并且当 allIds
或 byId
中的某些内容发生变化时仅 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]
。这样,如果类别发生变化,只有相应的子组件会重新渲染,而不是让父组件和所有子组件都重新渲染。