遵守 react-hooks/exhaustive-deps 会导致无限循环和/或大量 useCallback() [英] Obeying react-hooks/exhaustive-deps leads to infinite loops and/or lots of useCallback()

查看:202
本文介绍了遵守 react-hooks/exhaustive-deps 会导致无限循环和/或大量 useCallback()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

My app has a userService that exposes a useUserService hook with API functions such as getUser, getUsers, etc. I use a Hook for this because the API calls require information from my Session state, which is provided by a React Context Provider.

getUsers 函数提供给 useEffect 调用会使 react-hooks/exhaustive-deps eslint 规则不满意,因为它想要 getUsers 函数列为 dep - 然而,将其列为 dep 会导致效果在无限循环中运行,因为每次重新渲染组件时,它都会重新运行 useUserService 钩子,它重新创建了 getUsers 函数.

Providing the getUsers functions to a useEffect call makes the react-hooks/exhaustive-deps eslint rule unhappy, because it wants the getUsers function listed as a dep - however, listing it as a dep causes the effect to run in an infinite loop, because each time the component is re-rendered, it re-runs the useUserService hook, which recreates the getUsers function.

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

This can be remedied by wrapping the functions in useCallback, but then the useCallback dependency array runs into a similar lint rule. I figure I must be doing something wrong here, because I can't imagine I'm supposed to wrap every single one of these functions in useCallback().

我在 Codesandbox 中重现了这个问题.

I've recreated the issue in 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

2: Remedy eslint warning by sprinkling useCallback in, leading to another eslint warning: 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

3: Remedy that eslint rule by going deeper: https://codesandbox.io/s/usecallback-lint-part-3-6wfwj?file=/src/apiService.ts

如果我只是忽略 lint 警告,一切都正常……但是我应该这样做吗?

Everything works completely fine if I just ignore the lint warning... but should I?

推荐答案

如果您想保留您已经选择的确切 API 和约束,规范的解决方案strong>——你需要确保一切一路向下"周围有 useCallbackuseMemo.

If you want to keep the exact API and constraints you've already chosen, that is the canonical solution — you need to ensure that everything "all the way down" has useCallback or useMemo around it.

不幸的是,这是正确的:

So this is unfortunately correct:

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

I can't imagine I'm supposed to wrap every single one of these functions in useCallback()

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

There is a concrete practical reason for it. If something at the very bottom of the chain changes (in your example, it would be the "session state from React's context" you're referring to), you somehow need this change to propagate to the effects. Since otherwise they'd keep using stale context.

但是,我的一般建议是首先尽量避免构建这样的 API.特别是,构建一个像 useFetch 这样的 API,它接受一个任意函数作为回调,然后有效地调用它,提出了各种各样的问题.比如,如果该函数在某个改变的状态下关闭,你希望会发生什么?如果消费者传递一个始终不同"的内联函数怎么办?如果你只尊重初始功能,当你得到 cond 时,你会接受代码有问题吗?fn1 : fn2 作为参数?

However, my general suggestion would be to try to avoid building APIs like this in the first place. In particular, building an API like useFetch that takes an arbitrary function as a callback, and then calling it in effect, poses all sorts of questions. Like what do you want to happen if that function closes over some state that changes? What if the consumer passes an inline function that's always "different"? If you only respected the initial function, would you be OK with the code being buggy when you're getting cond ? fn1 : fn2 as an argument?

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

So, generally speaking, I would strongly discourage building a helper like this, and instead rethink the API so that you don't need to inject a "way to fetch" into a data fetching function, and instead that data fetching function knows how to fetch by itself.

TLDR:一个自定义 Hook 带有一个在效果中需要的函数,通常是不必要的通用,并导致像这样的复杂化.

TLDR: a custom Hook taking a function that is then needed inside of an effect is often unnecessarily generic and leads to complications like this.

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

For a concrete example of how you could write this differently:

  1. 首先,在顶层写下您的 API 函数.不像 Hooks——只是普通的顶级函数.无论他们需要什么(包括会话上下文"),都应该在他们的论据中.

// api.js

export function fetchUser(session, userId) {
  return axios(...)
}

  1. 创建 Hook 以从上下文中获取他们需要的任何数据.

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.

Here, api.fetchUser never "changes" so you don't have any useCallback at all.

我意识到我跳过了传递参数,例如 userId.您可以向 useFetch 添加一个 args 数组,该数组只接受可序列化的值,并在您的依赖项中使用 JSON.stringify(args).您仍然需要禁用该规则,但至关重要的是您遵守了规则的精神 — 指定了依赖项.这与为函数禁用它有很大不同,后者会导致细微的错误.

I realized I skipped passing the arguments through, like userId. You could add an args array to useFetch that only takes serializable values, and use JSON.stringify(args) in your dependencies. You'd still have to disable the rule but crucially you're complying with the spirit of the rule — dependencies are specified. Which is pretty different from disabling it for functions, which leads to subtle bugs.

这篇关于遵守 react-hooks/exhaustive-deps 会导致无限循环和/或大量 useCallback()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆