【问题标题】:Custom hook with useEffect (on mount) is executing more than once带有 useEffect 的自定义钩子(在挂载上)执行了多次
【发布时间】:2020-01-22 20:37:48
【问题描述】:

我有这个钩子,它在挂载时调用一个 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”,它会启动一个无限循环。

【问题讨论】:

  • 我们需要看看你的渲染方法

标签: reactjs use-effect


【解决方案1】:

我不明白为什么 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];

除非数据发生变化,否则 Redux 应该从 useSelector 返回相同的对象。

【讨论】:

  • 太棒了。谢谢你。现在,最好的方法是什么(如果有的话)来做到这一点。因为 useCategories 将在每次渲染时执行,所以 if(mainCategories) 将在每次渲染时为真(在获取它们之后)。我希望在它们被提取后只执行一次。使用 useMemo 将得到解决。但是有更好的方法吗?谢谢亚历克斯! @alex-wayne
  • 在最后查看我的编辑。我认为你在这里比你需要的更努力。
  • 现在我只带来 "state.categories.specialities" 。但我将不得不带来更多数据,并对其进行一些处理。一些过滤。这就是为什么我有额外的代码
猜你喜欢
  • 2019-12-27
  • 1970-01-01
  • 2021-11-22
  • 1970-01-01
  • 2020-05-19
  • 2021-10-09
  • 2020-07-14
  • 2022-09-27
相关资源
最近更新 更多