【问题标题】:Obeying react-hooks/exhaustive-deps leads to infinite loops and/or lots of useCallback()遵守 react-hooks/exhaustive-deps 会导致无限循环和/或大量 useCallback()
【发布时间】:2021-06-06 18:31:00
【问题描述】:

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

getUsers 函数提供给useEffect 调用会使react-hooks/exhaustive-deps eslint 规则不愉快,因为它希望将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:通过在useCallback 中喷洒来解决eslint 警告,导致另一个eslint 警告:https://codesandbox.io/s/usecallback-lint-part-2-uwhhf?file=/src/App.js

3:解决那个 eslint规则通过深入:https://codesandbox.io/s/usecallback-lint-part-3-6wfwj?file=/src/apiService.ts

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

【问题讨论】:

  • 禁用函数规则将 100% 导致错误,只要您从 props 中读取或在传递函数时“在路上”的某处状态。查看我的回复。

标签: reactjs react-hooks


【解决方案1】:

如果您想保留您已经选择的确切 API 和约束,规范的解决方案 - 您需要确保一切“一直向下” " 周围有 useCallbackuseMemo

不幸的是,这是正确的:

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

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

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

所以,一般来说,我强烈反对构建这样的帮助程序,而是重新考虑 API,这样您就不需要将“获取方式”注入数据获取函数,而是注入数据获取函数知道如何自行获取。

TLDR:一个自定义 Hook 采用一个函数,然后在效果内部需要该函数通常是不必要的通用并导致这样的复杂性。


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

  1. 首先,在顶层写下您的 API 函数。不像 Hooks——只是简单的顶级函数。无论他们需要什么(包括“会话上下文”)都应该在他们的论点中。
// api.js

export function fetchUser(session, userId) {
  return axios(...)
}
  1. 创建 Hooks 以从上下文中获取所需的任何数据。
function useSession() {
  return useContext(Session)
}
  1. 将这些东西组合在一起。
function useFetch(callApi) {
  const session = useSession()
  useEffect(() => {
    callApi(session).then(...)
    // ...
  }, [callApi, session])
}

import * as api from './api'

function MyComponent() {
  const data = useFetch(api.fetchUser)
}

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

编辑:我意识到我跳过了传递参数,比如userId。您可以将 args 数组添加到仅采用可序列化值的 useFetch,并在您的依赖项中使用 JSON.stringify(args)。您仍然必须禁用该规则,但至关重要的是您遵守了规则的精神——指定了依赖关系。这与为功能禁用它有很大不同,后者会导致一些细微的错误。

【讨论】:

  • 我添加了一个示例,说明如何重组它以将“裸”函数传递给 useEffect,即使它处于依赖关系中也没关系(因为它永远不会改变)。跨度>
猜你喜欢
  • 1970-01-01
  • 2020-11-20
  • 1970-01-01
  • 2021-04-15
  • 1970-01-01
  • 2020-04-03
  • 2020-06-08
  • 2021-05-23
  • 2020-05-01
相关资源
最近更新 更多