使用useEffect的自定义钩子(在挂载时)执行多次

时间:2020-01-22 20:37:48

标签: reactjs use-effect

我有这个钩子,在挂载时调用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)我不明白为什么use类别钩子不只执行一次?

这就是我对useCategories中的useEffect应该如何表现的了解。

2)如果我在SideBar.js中添加了useEffect挂钩中的依赖项“ categoriesData”,则会启动无限循环。

1 个答案:

答案 0 :(得分:2)

我不明白为什么useCategories挂钩不只执行一次?

所有钩子在每次渲染组件时都会执行,它们在执行时会做不同的事情。您的useCategories自定义挂钩是 just 一个函数,恰好调用了React提供的其他挂钩。

在这种情况下,每次渲染组件时,您都会得到:

[useCategories] --> Before Return

由于您正在渲染上调用函数useCategories,因此会记录该消息。

但是,您会注意到以下消息:

[useCategories] --> UseEffect

仅出现一次。这是因为useEffect钩子(如您所设置的那样)在第一次渲染之后将只运行一次。

因此,回答您的第一个问题,它的工作原理与您认为应该的一样。


如果我在SideEb.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返回相同的对象。