遵守react-hooks/exhaustive-deps会导致无限循环和/或大量useCallback()

luk*_*eau 7 reactjs react-hooks

我的应用程序有一个userService公开useUserService带有 API 函数(例如getUsergetUsers等)的钩子。我为此使用钩子是因为 API 调用需要来自我的会话状态的信息,该信息由 React Context Provider 提供。

getUsers向调用提供函数useEffect会使react-hooks/exhaustive-depseslint 规则不满意,因为它希望将getUsers函数列为 dep - 但是,将其列为 dep 会导致效果在无限循环中运行,因为每次重新渲染组件时,它都会重新运行useUserService钩子,从而重新创建该getUsers函数。

这可以通过将函数包装在 中来解决useCallback,但随后useCallback依赖项数组会遇到类似的 lint 规则。我想我一定是在这里做错了什么,因为我无法想象我应该将这些函数中的每一个都包装在useCallback().

我已经在 Codesandbox 中重新创建了该问题。

1:遇到eslint警告:https://codesandbox.io/s/usecallback-lint-part-1-76bcf ?file=/src/useFetch.ts

2:eslint通过撒入补救警告useCallback,导致另一个eslint警告: https: //codesandbox.io/s/usecallback-lint-part-2-uwhhf ?file=/src/App.js

3:通过更深入地补救该规则: https://codesandbox.io/s/usecallback-lint-part-3-6wfwj ?file=/src/apiService.tseslint

如果我忽略 lint 警告,一切都会正常工作......但我应该吗?

Dan*_*mov 9

如果您想保留您已经选择的确切 API 和约束,就是规范的解决方案\xe2\x80\x94,您需要确保“一直向下”的所有内容都具有useCallbackuseMemo围绕它。

\n

不幸的是,这是正确的:

\n
\n

我无法想象我应该将这些函数中的每一个都包装在 useCallback() 中

\n
\n

这是有具体实际原因的。如果链最底部的某些内容发生变化(在您的示例中,这将是您所指的“React 上下文中的会话状态”),您以某种方式需要此更改来传播效果。否则他们会继续使用陈旧的上下文。

\n

然而,我的一般建议是首先尝试避免构建这样的 API。特别是,构建这样的 APIuseFetch将任意函数作为回调,然后有效地调用它,会带来各种各样的问题。就像如果该函数在某些状态发生变化时关闭,您希望发生什么?如果消费者传递一个总是“不同”的内联函数怎么办?cond ? fn1 : fn2如果您只尊重初始功能,那么当您将其作为参数时,您是否可以接受代码有错误?

\n

因此,一般来说,我强烈建议不要构建这样的助手,而是重新考虑 API,这样您就不需要将“获取方式”注入到数据获取函数中,而数据获取函数知道如何执行自行获取。

\n

TLDR:自定义 Hook 获取效果中需要的函数通常是不必要的通用,并会导致像这样的复杂性。

\n
\n

有关如何以不同方式编写此内容的具体示例:

\n
    \n
  1. 首先,在顶层写下您的 API 函数。不像 Hooks \xe2\x80\x94 只是普通的顶级函数。他们需要的任何内容(包括“会话上下文”)都应该在他们的论点中。
  2. \n
\n
// api.js\n\nexport function fetchUser(session, userId) {\n  return axios(...)\n}\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. 创建 Hooks 以从 Context 获取所需的任何数据。
  2. \n
\n
function useSession() {\n  return useContext(Session)\n}\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. 将这些东西组合在一起。
  2. \n
\n
function useFetch(callApi) {\n  const session = useSession()\n  useEffect(() => {\n    callApi(session).then(...)\n    // ...\n  }, [callApi, session])\n}\n
Run Code Online (Sandbox Code Playgroud)\n

\n
import * as api from \'./api\'\n\nfunction MyComponent() {\n  const data = useFetch(api.fetchUser)\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在这里,永远不会“改变”,所以你根本api.fetchUser没有任何改变。useCallback

\n

编辑:我意识到我跳过了传递参数,比如userId. 您可以添加一个仅接受可序列化值的args数组,并在依赖项中使用。您仍然必须禁用该规则,但最重要的是您遵守规则的精神 \xe2\x80\x94 依赖项已指定useFetchJSON.stringify(args)。这与禁用它的功能有很大不同,后者会导致微妙的错误。

\n