【问题标题】:How to fix missing dependency warning when using useEffect React Hook使用 useEffect React Hook 时如何修复缺少的依赖警告
【发布时间】:2019-10-16 14:47:00
【问题描述】:

使用 React 16.8.6(在以前的版本 16.8.3 上很好),当我尝试防止获取请求上的无限循环时出现此错误:

./src/components/BusinessesList.js
Line 51:  React Hook useEffect has a missing dependency: 'fetchBusinesses'.
Either include it or remove the dependency array  react-hooks/exhaustive-deps

我一直无法找到停止无限循环的解决方案。我想远离使用useReducer()。我确实找到了这个讨论[ESLint] Feedback for 'exhaustive-deps' lint rule #14920,其中一个可能的解决方案是You can always // eslint-disable-next-line react-hooks/exhaustive-deps if you think you know what you're doing. 我对我正在做的事情没有信心,所以我还没有尝试实现它。

我有这个当前设置,React hook useEffect runs continuously forever/infinite loop,唯一的评论是关于我不熟悉的 useCallback()

我目前如何使用useEffect()(我只想在开始时运行一次,类似于componentDidMount()):

useEffect(() => {
    fetchBusinesses();
  }, []);
const fetchBusinesses = () => {
    return fetch("theURL", {method: "GET"}
    )
      .then(res => normalizeResponseErrors(res))
      .then(res => {
        return res.json();
      })
      .then(rcvdBusinesses => {
        // some stuff
      })
      .catch(err => {
        // some error handling
      });
  };

【问题讨论】:

  • 由于这个问题的访问量很大,这里有一个指向Dan Abramov's blog 的链接,他在其中详细解释了 useEffect 及其依赖关系。
  • 还有一个特性请求,所以 React 改进了 useEffect API 来避免这个问题,清楚地将效果触发器与效果依赖项分开:github.com/facebook/react/issues/22132 由于 Next.js 默认启用了 linting,所以这个警告必须出现百万次世界各地的一天,这必须以某种方式停止。
  • 同意,这在官方文档中完全不清楚。像 React 这样的库不应该需要论坛和博客文章来使其工作。

标签: reactjs eslint create-react-app react-hooks


【解决方案1】:

如果您没有在效果之外的任何地方使用 fetchBusinesses 方法,您可以简单地将其移动到效果中并避免警告

useEffect(() => {
    const fetchBusinesses = () => {
       return fetch("theURL", {method: "GET"}
    )
      .then(res => normalizeResponseErrors(res))
      .then(res => {
        return res.json();
      })
      .then(rcvdBusinesses => {
        // some stuff
      })
      .catch(err => {
        // some error handling
      });
  };
  fetchBusinesses();
}, []);

但是,如果您在渲染之外使用 fetchBusinesses,则必须注意两件事

  1. 您有什么问题在挂载期间使用 fetchBusinesses 作为方法并带有封闭闭包吗?
  2. 您的方法是否依赖于它从封闭闭包中接收到的一些变量?这不是你的情况。
  3. 在每次渲染时,都会重新创建 fetchBusinesses,因此将其传递给 useEffect 会导致问题。因此,如果要将 fetchBusinesses 传递给依赖数组,首先必须记住它。

总而言之,我想说,如果您在 useEffect 之外使用 fetchBusinesses,则可以使用 // eslint-disable-next-line react-hooks/exhaustive-deps 禁用规则,否则您可以将方法移到 useEffect 内

要禁用规则,你可以这样写

useEffect(() => {
   // other code
   ...

   // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) 

【讨论】:

  • 我使用了您很好地概述的解决方案。由于设置不同,我在其他地方使用的另一个解决方案是useCallback()。例如:const fetchBusinesses= useCallback(() => { ... }, [...])useEffect() 看起来像这样:useEffect(() => { fetchBusinesses(); }, [fetchBusinesses]);
  • @russ,你是对的,如果你要将它传递给依赖数组,你需要使用 useCallback 来记忆 fetchBusiness
  • 使用// eslint-disable-next-line react-hooks/exhaustive-deps 向 linter 解释您的代码是正确的就像是 hack。我希望他们会找到另一种解决方案,使 linter 更智能,以检测何时不是强制性的参数
  • @TapasAdhikary,是的,你可以在 useEffect 中使用异步函数,你只需要以不同的方式编写它。请查看stackoverflow.com/questions/53332321/…
  • linter 今天仍然很笨,如果您在使用外部变量时想要类似 componentDidMount 的行为(如果它们发生变化,需要其中一些但不是全部来触发重新渲染),无论如何您都会收到该警告你做了什么......至少我无法在网上找到解决方案
【解决方案2】:

编辑 09/20/2021

如果您正在创建新应用或具有足够的灵活性,那么状态管理库有很多不错的选择。看看 Recoil。

只是为了完整性:

1。 (停止工作)使用函数作为useEffect回调

useEffect(fetchBusinesses, [])

2。在useEffect() 内声明函数

useEffect(() => {
  function fetchBusinesses() {
    ...
  }
  fetchBusinesses()
}, [])

3。与useCallback() 记忆

在这种情况下,如果您的函数中有依赖项,则必须将它们包含在 useCallback 依赖项数组中,如果函数的参数更改,这将再次触发 useEffect。此外,它有很多样板......所以只需将函数直接传递给useEffect,就像1. useEffect(fetchBusinesses, [])一样。

const fetchBusinesses = useCallback(() => {
  ...
}, [])
useEffect(() => {
  fetchBusinesses()
}, [fetchBusinesses])

4。函数的默认参数

正如https://stackoverflow.com/a/61183162/5552686建议的那样

useEffect((fetchBusinesses = fetchBusinesses) => {
   fetchBusinesses();
}, []);

5。创建自定义挂钩

创建一个自定义钩子并在您只需要运行一次函数时调用它。它可能更清洁。你也可以返回一个回调来重置,在需要的时候重新运行“初始化”。

// customHooks.js
const useInit = (callback, ...args) => {
  const [mounted, setMounted] = useState(false)
  
  const resetInit = () => setMounted(false)

  useEffect(() => {
     if(!mounted) {
        setMounted(true);
        callback(...args);
     }
  },[mounted, callback]);

  return [resetInit]
}

// Component.js
return ({ fetchBusiness, arg1, arg2, requiresRefetch }) => {
  const [resetInit] = useInit(fetchBusiness, arg1, arg2)

  useEffect(() => {
    resetInit()
  }, [requiresRefetch, resetInit]);

6。禁用 eslint 的警告

禁用警告应该是你最后的手段,但是当你这样做时,最好内联并明确,因为未来的开发人员可能会感到困惑或在不知道 linting 已关闭的情况下创建意外错误

useEffect(() => {
  fetchBusinesses()
}, []) // eslint-disable-line react-hooks/exhaustive-deps

原始回复

可以直接设置为useEffect回调:

useEffect(fetchBusinesses, [])

它只会触发一次,因此请确保正确设置所有函数的依赖项(与使用componentDidMount/componentWillMount...相同)

【讨论】:

  • 解除 eslint 的警告可以吗?
  • 我更喜欢禁用警告作为最后的手段,因为未来的开发人员可能会感到困惑或在不知道 linting 已关闭的情况下创建意外错误
  • useEffect(fetchBusinesses, []) 会抛出 "TypeError: 一个效果函数不能返回除用于清理的函数之外的任何东西。" 因为fetchBusinesses 返回一个promise .
  • 第一个建议居然没有消除警告
  • @LewyBlue 那是因为您在依赖项行上方添加了注释
【解决方案3】:
./src/components/BusinessesList.js
Line 51:  React Hook useEffect has a missing dependency: 'fetchBusinesses'.
Either include it or remove the dependency array  react-hooks/exhaustive-deps

这不是 JavaScript/React 错误,而是 ESLint (eslint-plugin-react-hooks) 警告。

它告诉你这个钩子依赖于函数fetchBusinesses,所以你应该把它作为一个依赖传递。

useEffect(() => {
  fetchBusinesses();
}, [fetchBusinesses]);

如果函数在如下组件中声明,则可能导致在每次渲染时调用该函数:

const Component = () => {
  /*...*/

  // New function declaration every render
  const fetchBusinesses = () => {
    fetch('/api/businesses/')
      .then(...)
  }

  useEffect(() => {
    fetchBusinesses();
  }, [fetchBusinesses]);

  /*...*/
}

因为每次函数都会用新的引用重新声明。

做这件事的正确方法是:

const Component = () => {
  /*...*/

  // Keep the function reference
  const fetchBusinesses = useCallback(() => {
    fetch('/api/businesses/')
      .then(...)
  }, [/* Additional dependencies */])

  useEffect(() => {
    fetchBusinesses();
  }, [fetchBusinesses]);

  /*...*/
}

或者只是在useEffect中定义函数。

更多:[ESLint] Feedback for 'exhaustive-deps' lint rule #14920

【讨论】:

  • 解决方案很好,如果在函数上修改另一个状态,则必须添加依赖项以避免另一个意外行为
【解决方案4】:

React 也给出了解决方案。他们建议您使用useCallback,它将返回您的函数的memoize 版本:

“fetchBusinesses”函数使 useEffect Hook(在第 NN 行)的依赖关系在每次渲染时都发生变化。为了解决这个问题,将 'fetchBusinesses' 定义包装到它自己的 useCallback() Hook react-hooks/exhaustive-deps 中

useCallback 使用简单,因为它与useEffect 具有相同的签名。不同之处在于 useCallback 返回一个函数。 它看起来像这样:

 const fetchBusinesses = useCallback( () => {
        return fetch("theURL", {method: "GET"}
    )
    .then(() => { /* Some stuff */ })
    .catch(() => { /* Some error handling */ })
  }, [/* deps */])
  // We have a first effect that uses fetchBusinesses
  useEffect(() => {
    // Do things and then fetchBusinesses
    fetchBusinesses();
  }, [fetchBusinesses]);
   // We can have many effects that use fetchBusinesses
  useEffect(() => {
    // Do other things and then fetchBusinesses
    fetchBusinesses();
  }, [fetchBusinesses]);

【讨论】:

  • 就我而言,这个useCallBack 钩子解决了我的问题。要查看详细信息,请访问documentation
【解决方案5】:

这些警告对于查找更新不一致的组件非常有帮助:Is it safe to omit functions from the list of dependencies?

但是,如果您想在整个项目中删除警告,您可以将其添加到您的 ESLint 配置中:

  {
  "plugins": ["react-hooks"],
  "rules": {
    "react-hooks/exhaustive-deps": 0
    }
  }

【讨论】:

    【解决方案6】:
    const [mount, setMount] = useState(false)
    const fetchBusinesses = () => {
       // Function definition
    }
    useEffect(() => {
       if(!mount) {
          setMount(true);
          fetchBusinesses();
       }
    },[fetchBusinesses, mount]);
    

    这个解决方案非常简单,您不需要覆盖 ESLint 警告。只需维护一个标志来检查组件是否已安装。

    【讨论】:

    • 而你每次需要componentDidMount的时候都会这样做?
    • 这将标记您需要添加 mount 作为 useEffect 的依赖项。
    【解决方案7】:

    只为下一行禁用 ESLint;

    useEffect(() => {
       fetchBusinesses();
    // eslint-disable-next-line
    }, []);
    

    通过这种方式,您可以像使用组件一样使用它(调用一次)。

    更新

    const fetchBusinesses = useCallback(() => {
     // Your logic in here
     }, [someDeps])
    
    useEffect(() => {
       fetchBusinesses();
    // No need to skip the ESLint warning
    }, [fetchBusinesses]);
    

    fetchBusinesses 将在每次 someDeps 发生变化时被调用。

    【讨论】:

    • 而不是禁用,只需这样做:[fetchBusinesses] 将自动删除警告并为我解决了问题。
    • @RotimiBest - 这样做会导致问题中描述的无限重新渲染
    • 我实际上是在不久前的一个项目中这样做的,它并没有产生无限循环。不过我会再检查一次。
    • @user210757 等等,为什么会导致死循环,不像你在从服务器获取数据后设置状态。如果您要更新状态,只需在调用 useEffect 中的函数之前编写一个 if 条件,以检查状态是否为空。
    • @rotimi-best 自从我发表评论以来一直很好,但我会说函数每次都会重新创建,因此永远不会相同,所以总是会重新渲染,除非你进入 useEffect 主体或 useCallback
    【解决方案8】:

    这篇文章是一个很好的关于使用钩子获取数据的入门文章:https://www.robinwieruch.de/react-hooks-fetch-data/

    本质上,在useEffect中包含fetch函数定义:

    useEffect(() => {
      const fetchBusinesses = () => {
        return fetch("theUrl"...
          // ...your fetch implementation
        );
      }
    
      fetchBusinesses();
    }, []);
    

    【讨论】:

      【解决方案9】:

      你试试这个方法:

      const fetchBusinesses = () => {
          return fetch("theURL", {method: "GET"})
              .then(res => normalizeResponseErrors(res))
              .then(res => {
                  return res.json();
              })
              .then(rcvdBusinesses => {
                  // Some stuff
              })
              .catch(err => {
                  // Some error handling
              });
        };
      

      useEffect(() => {
          fetchBusinesses();
      });
      

      它适合你。

      但我的建议是尝试这种方式,它也适合你。 这比以前的方法好。我是这样用的:

      useEffect(() => {
          const fetchBusinesses = () => {
              return fetch("theURL", {method: "GET"})
                  .then(res => normalizeResponseErrors(res))
                  .then(res => {
                      return res.json();
                  })
                  .then(rcvdBusinesses => {
                      // Some stuff
                  })
                  .catch(err => {
                      // Some error handling
                  });
          };
      
          fetchBusinesses();
      }, []);
      

      如果你获取基于特定 id 的数据,则添加回调 useEffect [id]。然后它无法向您显示警告 React Hook useEffect has a missing dependency: 'any thing'. Either include it or remove the dependency array

      【讨论】:

        【解决方案10】:

        您可以删除第二个参数类型数组[],但每次更新时也会调用fetchBusinesses()。如果您愿意,可以将IF 语句添加到fetchBusinesses() 实现中。

        React.useEffect(() => {
          fetchBusinesses();
        });
        

        另一种是在你的组件之外实现fetchBusinesses() 函数。只是不要忘记将任何依赖项参数传递给您的 fetchBusinesses(dependency) 调用(如果有)。

        function fetchBusinesses (fetch) {
          return fetch("theURL", { method: "GET" })
            .then(res => normalizeResponseErrors(res))
            .then(res => res.json())
            .then(rcvdBusinesses => {
              // some stuff
            })
            .catch(err => {
              // some error handling
            });
        }
        
        function YourComponent (props) {
          const { fetch } = props;
        
          React.useEffect(() => {
            fetchBusinesses(fetch);
          }, [fetch]);
        
          // ...
        }
        

        【讨论】:

        • 删除依赖数组括号会导致在我有表单的组件中无限重新渲染的问题!
        【解决方案11】:

        实际上,当您使用钩子进行开发时,警告非常有用。但在某些情况下,它会刺伤你。尤其是当你不需要监听依赖变化时。

        如果您不想将 fetchBusinesses 放入钩子的依赖项中,您可以简单地将其作为参数传递给钩子的回调并将主要的 fetchBusinesses 设置为它的默认值,如下所示:

        useEffect((fetchBusinesses = fetchBusinesses) => {
           fetchBusinesses();
        }, []);
        

        这不是最佳做法,但在某些情况下可能会有用。

        另外,作为Shubham wrote,您可以添加以下代码来告诉 ESLint 忽略对您的钩子的检查。

        // eslint-disable-next-line react-hooks/exhaustive-deps
        

        【讨论】:

          【解决方案12】:

          好吧,如果你想以不同的方式研究这个问题,你只需要知道 React 有哪些非 exhaustive-deps 的选项。您不应该在效果中使用闭包函数的原因之一是在每次渲染时,它都会被重新创建/销毁。

          因此,钩子中有多个 React 方法被认为是稳定且未用尽的,您不必应用到 useEffect 依赖项,反过来也不会破坏 react-hooks/exhaustive-deps 的规则参与。例如useReduceruseState的第二个返回变量是一个函数。

          const [,dispatch] = useReducer(reducer, {});
          
          useEffect(() => {
              dispatch(); // Non-exhausted - ESLint won't nag about this
          }, []);
          

          因此,反过来,您可以让所有外部依赖项与您的 reducer 函数中的当前依赖项共存。

          const [,dispatch] = useReducer((current, update) => {
              const { foobar } = update;
              // Logic
          
              return { ...current, ...update };
          }), {});
          
          const [foobar, setFoobar] = useState(false);
          
          useEffect(() => {
              dispatch({ foobar }); // non-exhausted `dispatch` function
          }, [foobar]);
          

          【讨论】:

            【解决方案13】:

            在我的例子中,我的局部变量 organization 有这个警告,当我将 organization 放入依赖数组时,useEffect 将无限获取。因此,如果您遇到像我这样的问题,请将 useEffect 与依赖数组一起使用并拆分:

            因为如果您有多个修改状态的 API 调用,它会多次调用 useEffect

            发件人:

              const { organization } = useSelector(withOrganization)
              const dispatch = useDispatch()
            
              useEffect(() => {
                dispatch(getOrganization({}))
                dispatch(getSettings({}))
                dispatch(getMembers({}))
              }, [dispatch, organization])
            

            收件人:

              const { organization } = useSelector(withOrganization)
              const dispatch = useDispatch()
            
              useEffect(() => {
                dispatch(getOrganization({}))
                dispatch(getSettings({}))
              }, [dispatch, organization])
            
              useEffect(() => {
                dispatch(getMembers({}))
              }, [dispatch])
            

            【讨论】:

              【解决方案14】:

              如果您在 useEffect 中使用的变量在组件内定义或作为道具传递给组件,则会发生此警告。由于您在同一个组件中定义了fetchBusinesses(),因此您必须将其传递给依赖数组。

              但是如果你导入了fetchBusinesses() 然后在useEffect 中使用它,你就不需要将它添加到依赖数组中。这就是我们实际设置Redux 应用程序的方式:我们总是导入我们的动作创建者并在useEffect 中运行它,而不将其添加到依赖数组中。

              useMemo 也是如此。

              【讨论】:

              • 即使您从商店导入函数,您仍然会收到警告。因为您的函数将通过 mapDispatchToProps 调用或使用 connect 标签的第二个参数传递给 props 对象。 connect(mapStateToProps, {fetchBusinesses})(Component)
              • @osmancakirio 在这种情况下,您找到解决警告的方法了吗?我也有同样的问题...
              • @ndtreviv 我重构了组件以现在使用 redux-hooks 而不是 connect 标签。然后我将调度函数提供给依赖数组。 redux 开发人员也推荐它,因为他们说这样做是安全的,因为对 dispatch 函数的引用几乎永远不会改变。
              【解决方案15】:

              您可以通过传递对它的引用来消除此 Es-lint 警告:

              下面提到的示例,但是您可以在此链接上观看解决方案:https://www.youtube.com/watch?v=r4A46oBIwZk&t=8s

              警告: 第 13:8 行:React Hook React.useEffect 缺少依赖项:'history' 和 'currentUser?.role'。要么包含它们,要么删除依赖数组 react-hooks/exhaustive-deps

              React.useEffect(() => {
                  if (currentUser?.role !== "Student") {
                      return history.push("/")
                  }
              }, [])
              

              分辨率: 第 1 步:将业务逻辑移到单独的 const 中。

              现在警告是:React Hook React.useEffect 缺少依赖项:'roleChecking'。

              const roleChecking = () =>{
                 if (currentUser?.role !== "Student") {
                      return history.push("/")
                  }
              }
              
              React.useEffect(() => {
                  roleChecking()
              }, [])
              

              最后一步是创建对函数的引用:

                const roleRef = React.useRef();
              
                const roleChecking = () => {
                  if (currentUser?.role !== "Student") {
                    return history.push("/");
                  }
                };
                roleRef.current = roleChecking;
              
                React.useEffect(() => {
                 return roleRef.current();
                }, [currentUser?.role]);
              

              【讨论】:

                【解决方案16】:

                似乎在组件中声明了fetchBusinesses 函数。 这意味着在每次渲染中它都会声明触发钩子的新函数。

                有两种方法可以解决此问题。

                1. fetchBusinesses 函数声明移出组件。

                2. useCallback 钩子包裹fetchBusinesses 函数。

                首选第一个选项。

                【讨论】:

                  【解决方案17】:

                  这不是特定于问题用例的答案,而是更一般的情况,并涵盖了 useEffect 或 extract and import 不工作的情况。 useRef 场景:

                  有时情况是 useEffect 应该有一个空数组,你仍然想在状态的 useEffect 部分中使用,但你仍然不想将它们作为依赖项注入,你也可以尝试 useCallback 并且现在反应是抱怨关于 useCallback 的依赖关系,你卡住了。 在这种情况下,您可以使用 useRef。例如:

                  const locationRef = useRef(location);
                  useEffect(()=>{
                  const qs = locationRef.current.search
                  ...
                  },[])
                  

                  使用此技术时应小心,并注意 useRef 不会激活渲染进程。

                  【讨论】:

                    【解决方案18】:

                    只需将函数作为参数传递到 useEffect 的数组中...

                    useEffect(() => {
                       functionName()
                    }, [functionName])
                    

                    【讨论】:

                      【解决方案19】:

                      使用 UseEffect fetchBusinesses 调用函数在 useEffect() 中声明,在调用函数名称之后声明一个 const 变量,

                      useEffect(()=>{
                      const fetchBusinesses=()=>{
                         console.log(useeffect fetchbussinesses functions)
                      }
                       fetchBusinesses();
                      },[declare the variable used in useeffect hooks ])
                      

                      【讨论】:

                        猜你喜欢
                        • 2021-12-07
                        • 1970-01-01
                        • 2022-08-14
                        • 2020-11-21
                        相关资源
                        最近更新 更多