带有 useEffect 的自定义挂钩(在装载时)执行了不止一次
Custom hook with useEffect (on mount) is executing more than once
我有这个挂钩,它在挂载时调用 redux 操作。所以我的猜测是它应该被执行一次。但事实并非如此。
useCategories.js
const useCategories = () => {
/*
Hook to call redux methods that will fetch the categories
of the exercises. Store it in redux store.
RETURN: loading, error, data
*/
const mainCategories = useSelector(state => state.categories.specialities);
const loading = useSelector(state => state.categories.loading);
const error = useSelector(state => state.categories.error);
const dispatch = useDispatch();
const onInitExercises = useCallback(
() => dispatch(actions.fetchCategories()),
[dispatch]
);
useEffect(() => {
console.log("[useCategories] --> UseEffect");
onInitExercises();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [onInitExercises]);
const data = { specialities: [] };
if (mainCategories) {
console.log("Inside If mainCategories");
for (let key in mainCategories) {
data["specialities"].push(mainCategories[key]);
}
}
console.log("[useCategories] --> Before Return");
return [loading, error, data];
};
export default useCategories;
现在我在另一个组件中使用它
边栏
import React, { useState, useEffect, useRef } from "react";
import useCategories from "../hookFetchCategories/useCategories";
const SideBarFilters = props => {
const [orderForm, setOrderForm] = useState({})
// Hook to fetch categories from DB
const [loading, error, categoriesData] = useCategories();
// Function to update form after categories are fetched
const updateSpecialitiesData = useRef((formElementKey, data) => {
console.log("Inside Update");
// code to update orderForm with data fetched in useCategories()
setOrderForm(orderFormUpdated);
});
useEffect(() => {
if (!loading && categoriesData && categoriesData["specialities"].length) {
console.log("Inside IF");
updateSpecialitiesData("specialities", categoriesData["specialities"]);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loading, updateSpecialitiesData]);
// --- Rest of the component
}
当我 运行 应用程序时,在 DEV 控制台中我有这个控制台日志
[useCategories] --> Before Return
useCategories.js:23 [useCategories] --> UseEffect
useCategories.js:53 [useCategories] --> Before Return
useCategories.js:53 [useCategories] --> Before Return
useCategories.js:42 Inside if mainCategories
useCategories.js:53 [useCategories] --> Before Return
SideBarFilters.js:61 Inside IF
SideBarFilters.js:36 Inside Update
useCategories.js:42 Inside if mainCategories
useCategories.js:53 [useCategories] --> Before Return
1) 我不明白为什么 useCategories 钩子不是只执行一次?
这就是我对 useCategories 中 useEffect 的行为方式的理解。
2) 如果我在 useEffect 挂钩中添加 SideBar.js,依赖项 "categoriesData",它会启动一个无限循环。
I do not understand why the useCategories hook is not executing only once?
每次渲染组件时都会执行所有挂钩,它们只是在执行时做不同的事情。您的 useCategories
自定义挂钩 只是 一个恰好调用 React 提供的其他挂钩的函数。
在这种情况下,每次渲染组件时,您都会得到一个:
[useCategories] --> Before Return
因为您在渲染时调用函数 useCategories
,它会记录该消息。
但是,您会注意到这条消息:
[useCategories] --> UseEffect
只出现过一次。这是因为 useEffect
挂钩(如您所设置的那样)只会 运行 一次,就在第一次渲染之后。
因此,为了回答您的第一个问题,它的工作方式与您认为的完全一样。
If I add in SideBar.js, in the useEffect hook, the dependency categoriesData
, it starts an infinite loop.
依赖关系是按标识而不是值进行比较的。认为 ===
而不是 ==
。这意味着,在对象的情况下,比较要求 "is this the same object? If not, run the effect again.".
如果您查看自定义挂钩,您会看到这一行:
const data = { specialities: [] };
这会在每次挂钩 运行 时(即每次渲染)创建一个新对象。尽管它可能具有相同的值,但每次渲染时它都是不同的对象。这意味着它会在每次渲染时使依赖数组无效。
有很多方法可以解决此问题,但一种方法是 useMemo
,它只会在会更改其值的值更改时创建一个新对象。
const data = useMemo(() => {
const myFancyData = {} // logic to create your data here
return myFancyData
}, [dependenciesFor, above, logic, here])
或者想一想,您为什么要重建这些数据? data
只是 mainCategories
的手动构建克隆,所以为什么不只是:
return [loading, error, mainCategories];
Redux 应该 return 与 useSelector
相同的对象,除非数据已更改。
我有这个挂钩,它在挂载时调用 redux 操作。所以我的猜测是它应该被执行一次。但事实并非如此。
useCategories.js
const useCategories = () => {
/*
Hook to call redux methods that will fetch the categories
of the exercises. Store it in redux store.
RETURN: loading, error, data
*/
const mainCategories = useSelector(state => state.categories.specialities);
const loading = useSelector(state => state.categories.loading);
const error = useSelector(state => state.categories.error);
const dispatch = useDispatch();
const onInitExercises = useCallback(
() => dispatch(actions.fetchCategories()),
[dispatch]
);
useEffect(() => {
console.log("[useCategories] --> UseEffect");
onInitExercises();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [onInitExercises]);
const data = { specialities: [] };
if (mainCategories) {
console.log("Inside If mainCategories");
for (let key in mainCategories) {
data["specialities"].push(mainCategories[key]);
}
}
console.log("[useCategories] --> Before Return");
return [loading, error, data];
};
export default useCategories;
现在我在另一个组件中使用它
边栏
import React, { useState, useEffect, useRef } from "react";
import useCategories from "../hookFetchCategories/useCategories";
const SideBarFilters = props => {
const [orderForm, setOrderForm] = useState({})
// Hook to fetch categories from DB
const [loading, error, categoriesData] = useCategories();
// Function to update form after categories are fetched
const updateSpecialitiesData = useRef((formElementKey, data) => {
console.log("Inside Update");
// code to update orderForm with data fetched in useCategories()
setOrderForm(orderFormUpdated);
});
useEffect(() => {
if (!loading && categoriesData && categoriesData["specialities"].length) {
console.log("Inside IF");
updateSpecialitiesData("specialities", categoriesData["specialities"]);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loading, updateSpecialitiesData]);
// --- Rest of the component
}
当我 运行 应用程序时,在 DEV 控制台中我有这个控制台日志
[useCategories] --> Before Return
useCategories.js:23 [useCategories] --> UseEffect
useCategories.js:53 [useCategories] --> Before Return
useCategories.js:53 [useCategories] --> Before Return
useCategories.js:42 Inside if mainCategories
useCategories.js:53 [useCategories] --> Before Return
SideBarFilters.js:61 Inside IF
SideBarFilters.js:36 Inside Update
useCategories.js:42 Inside if mainCategories
useCategories.js:53 [useCategories] --> Before Return
1) 我不明白为什么 useCategories 钩子不是只执行一次?
这就是我对 useCategories 中 useEffect 的行为方式的理解。
2) 如果我在 useEffect 挂钩中添加 SideBar.js,依赖项 "categoriesData",它会启动一个无限循环。
I do not understand why the useCategories hook is not executing only once?
每次渲染组件时都会执行所有挂钩,它们只是在执行时做不同的事情。您的 useCategories
自定义挂钩 只是 一个恰好调用 React 提供的其他挂钩的函数。
在这种情况下,每次渲染组件时,您都会得到一个:
[useCategories] --> Before Return
因为您在渲染时调用函数 useCategories
,它会记录该消息。
但是,您会注意到这条消息:
[useCategories] --> UseEffect
只出现过一次。这是因为 useEffect
挂钩(如您所设置的那样)只会 运行 一次,就在第一次渲染之后。
因此,为了回答您的第一个问题,它的工作方式与您认为的完全一样。
If I add in SideBar.js, in the useEffect hook, the dependency
categoriesData
, it starts an infinite loop.
依赖关系是按标识而不是值进行比较的。认为 ===
而不是 ==
。这意味着,在对象的情况下,比较要求 "is this the same object? If not, run the effect again.".
如果您查看自定义挂钩,您会看到这一行:
const data = { specialities: [] };
这会在每次挂钩 运行 时(即每次渲染)创建一个新对象。尽管它可能具有相同的值,但每次渲染时它都是不同的对象。这意味着它会在每次渲染时使依赖数组无效。
有很多方法可以解决此问题,但一种方法是 useMemo
,它只会在会更改其值的值更改时创建一个新对象。
const data = useMemo(() => {
const myFancyData = {} // logic to create your data here
return myFancyData
}, [dependenciesFor, above, logic, here])
或者想一想,您为什么要重建这些数据? data
只是 mainCategories
的手动构建克隆,所以为什么不只是:
return [loading, error, mainCategories];
Redux 应该 return 与 useSelector
相同的对象,除非数据已更改。