reactjs - 带有 useEffect 的自定义钩子(在挂载上)执行了多次
问题描述
我有这个钩子,它在 mount 上调用一个 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)如果我添加 SideBar.js,在 useEffect 钩子中,依赖“categoriesData”,它会启动一个无限循环。
解决方案
我不明白为什么 useCategories 钩子不只执行一次?
每次渲染组件时,所有钩子都会执行,它们只是在执行时做不同的事情。您的useCategories
自定义钩子只是一个恰好调用 React 提供的其他钩子的函数。
在这种情况下,每次渲染组件时,您都会得到:
[useCategories] --> Before Return
因为您useCategories
在渲染时调用了一个函数,所以该函数会记录该消息。
但是,您会注意到此消息:
[useCategories] --> UseEffect
只出现一次。这是因为useEffect
钩子(正如您设置的那样)只会在第一次渲染之后运行一次。
因此,要回答您的第一个问题,它的工作方式与您认为应该工作的方式完全相同。
如果我在 SideBar.js 中添加 useEffect 钩子中的依赖项
categoriesData
,它会启动一个无限循环。
依赖关系是通过身份而不是值来比较的。认为===
没有==
。这意味着,在对象的情况下,比较是在询问“这是同一个对象吗?如果不是,请再次运行效果。”。
如果您查看您的自定义钩子,您会看到这一行:
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];
useSelector
除非数据发生变化,否则Redux 应该返回相同的对象。
推荐阅读
- linux - 使用终端从目录中的多个文件中删除所有非 ASCII 字符
- php - Laravel 使用 Redis 驱动程序的所有会话 ID
- laravel - 如何查看超过3个表的所有字段laravel
- javascript - 在 React 中使用 map 渲染元素
- typescript - 如何在 oclif-cli 中的表中显示 db 检索到的数据,如 mysql-client-cli?
- java - 无法从 Firestore 数据库中读取数据(Android 应用程序)
- php - 更改 foreach 语句中的 url
- angular - 如何使用 Angular 2+ 在 ReactiveForms 中管理动态验证器?
- c# - 如何获取 azure 文件共享存储 c# 中子文件夹内的所有文件?
- yeoman-generator - 带有 react/redux 的 YO .net core 2.x 演示项目?