简体   繁体   English

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

[英]Obeying react-hooks/exhaustive-deps leads to infinite loops and/or lots of useCallback()

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. 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.

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.getUsers函数提供给useEffect调用会使react-hooks/exhaustive-deps eslint 规则不满意,因为它希望将getUsers function 列为 dep - 但是,将其列为 dep 会导致效果在无限循环中运行,因为每次重新渲染组件时,它都会重新运行useUserService挂钩,从而重新创建getUsers function。

This can be remedied by wrapping the functions in useCallback , but then the useCallback dependency array runs into a similar lint rule.这可以通过将函数包装在useCallback中来解决,但是useCallback依赖数组会遇到类似的 lint 规则。 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() .我想我一定是在这里做错了,因为我无法想象我应该将这些函数中的每一个都包装在useCallback()中。

I've recreated the issue in Codesandbox.我在 Codesandbox 中重新创建了这个问题。

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

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 2:通过撒上useCallback来解决eslint警告,导致另一个eslint警告: https://codesandbox.io/s/usecallback-lint-part-2-uwhhf?file=/src/App.js

3: Remedy that eslint rule by going deeper: https://codesandbox.io/s/usecallback-lint-part-3-6wfwj?file=/src/apiService.ts 3:通过更深入地解决eslint规则: https://codesandbox.io/s/usecallback-lint-part-3-6wfwj?file=/src/apiService.ts

Everything works completely fine if I just ignore the lint warning... but should I?如果我只是忽略 lint 警告,一切都会正常工作......但我应该吗?

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.如果你想保持准确的 API 和你已经选择的约束,规范的解决方案——你需要确保所有“一路下来”的东西都有useCallbackuseMemo

So this is unfortunately correct:所以不幸的是,这是正确的:

I can't imagine I'm supposed to wrap every single one of these functions in useCallback()我无法想象我应该将这些函数中的每一个都包装在 useCallback()

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.如果链最底部的某些内容发生了变化(在您的示例中,它将是您所指的“来自 React 上下文的会话 state”),您需要以某种方式将此更改传播到效果。 Since otherwise they'd keep using stale context.否则他们会继续使用陈旧的上下文。

However, my general suggestion would be to try to avoid building APIs like this in the first place .但是,我的一般建议是首先尽量避免构建这样的 API 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.特别是,构建一个像useFetch这样的 API 将任意 function 作为回调,然后有效地调用它,提出了各种各样的问题。 Like what do you want to happen if that function closes over some state that changes?比如,如果 function 关闭了一些发生变化的 state,你发生什么? What if the consumer passes an inline function that's always "different"?如果消费者传递一个总是“不同”的内联 function 怎么办? If you only respected the initial function, would you be OK with the code being buggy when you're getting cond? fn1: fn2如果您只尊重最初的 function,那么当您得到cond? fn1: fn2 cond? fn1: fn2 as an argument? cond? fn1: fn2作为参数?

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. 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自己取。

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. TLDR:采用 function 的自定义 Hook,然后在效果内部需要它通常是不必要的通用,并导致这样的并发症。


For a concrete example of how you could write this differently:有关如何以不同方式编写此代码的具体示例:

  1. First, write down your API functions at top level.首先,在顶层写下您的 API 函数。 Not as Hooks — just plain top-level functions.不像 Hooks——只是简单的顶级函数。 Whatever they need (including "session context") should be in their arguments.他们需要的任何东西(包括“会话上下文”)都应该在他们的 arguments 中。
// api.js

export function fetchUser(session, userId) {
  return axios(...)
}
  1. Create Hooks to get any data they need from the Context.创建 Hooks 以从 Context 中获取所需的任何数据。
function useSession() {
  return useContext(Session)
}
  1. Compose these things together.把这些东西组合在一起。
function useFetch(callApi) {
  const session = useSession()
  useEffect(() => {
    callApi(session).then(...)
    // ...
  }, [callApi, session])
}

And

import * as api from './api'

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

Here, api.fetchUser never "changes" so you don't have any useCallback at all.在这里, api.fetchUser永远不会“改变”,所以你根本没有任何useCallback

Edit: I realized I skipped passing the arguments through, like userId .编辑:我意识到我跳过了通过 arguments ,比如userId You could add an args array to useFetch that only takes serializable values, and use JSON.stringify(args) in your dependencies.您可以将args数组添加到仅采用可序列化值的useFetch ,并在您的依赖项中使用JSON.stringify(args) 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.这与为功能禁用它有很大不同,后者会导致细微的错误。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 useCallBack react-hooks/exhaustive-deps 警告 - useCallBack react-hooks/exhaustive-deps warning 使用 useCallback 响应无限更新循环(react-hooks/exhaustive-deps) - React infinite update loop with useCallback (react-hooks/exhaustive-deps) react-hooks/exhaustive-deps 警告还是无限循环? - react-hooks/exhaustive-deps warning or infinite loop? react-hooks/exhaustive-deps 警告 - react-hooks/exhaustive-deps warning React挂钩:如何使用react-hooks / exhaustive-deps规则在没有无限循环的情况下读取和更新挂钩中的状态 - React hooks: How to read & update state in hooks without infinite loops with react-hooks/exhaustive-deps rule 是否可以通过 useCallback 避免自定义 React Hook 上的“eslint(react-hooks/exhaustive-deps)”错误? - Is it possible to avoid 'eslint(react-hooks/exhaustive-deps)' error on custom React Hook with useCallback? 由 axios 取消令牌引起的 react useEffect hook 无限循环 [react-hooks/exhaustive-deps] - react useEffect hook infinite loop caused by axios cancel token [react-hooks/exhaustive-deps] 是否有一个带有useEffect的无限滚动的实现,它不会触发react-hooks / exhaustive-deps lint警告? - Is there an implementation of infinite scroll with useEffect that doesn't trigger the react-hooks/exhaustive-deps lint warning? ESLint 希望 setSate 作为 useEffect 的依赖,但这会导致无限循环(react-hooks/exhaustive-deps) - ESLint wants setSate as a dependency for useEffect but this causes an infinite loop (react-hooks/exhaustive-deps) 反应 useEffect 带有警告的钩子 react-hooks/exhaustive-deps - React useEffect hook with warning react-hooks/exhaustive-deps
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM