如何避免“React Hook useEffect 缺少依赖项”和无限循环

Posted

技术标签:

【中文标题】如何避免“React Hook useEffect 缺少依赖项”和无限循环【英文标题】:How to avoid "React Hook useEffect has a missing dependency" and infinite loops 【发布时间】:2021-09-15 17:01:36 【问题描述】:

我写了一个这样的 react-js 组件:

import Auth from "third-party-auth-handler";
import  AuthContext  from "../providers/AuthProvider";

export default function MyComponent() 
  const  setAuth  = useContext(AuthContext);

  useEffect(() => 
    Auth.isCurrentUserAuthenticated()
      .then(user => 
        setAuth(isAuthenticated: true, user);
    )
    .catch(err => console.error(err));
  , []);
;

使用以下 AuthProvider 组件:

import React,  useState, createContext  from "react";

const initialState =  isAuthenticated: false, user: null ;
const AuthContext = createContext(initialState);

const AuthProvider = (props) => 
  const [auth, setAuth] = useState(initialState);

  return (
    <AuthContext.Provider value= auth, setAuth >
      props.children
    </AuthContext.Provider>
  )
;

export  AuthProvider, AuthContext ;

一切正常,但我在开发者控制台中收到此警告:

React Hook useEffect 缺少一个依赖项:'setAuth'。要么包含它,要么移除依赖数组 react-hooks/exhaustive-deps

如果我将 setAuth 添加为 useEffect 的依赖项,警告就会消失,但我会得到 useEffect() 以无限循环运行,并且应用程序会崩溃。 我知道这可能是因为每次安装组件时都会重新实例化setAuth。 我还想我可能应该使用useCallback() 来避免每次都重新实例化该函数,但我真的不明白如何将useCallbackuseContext() 中的函数一起使用

【问题讨论】:

你能显示 AuthContext 代码吗? 为了向您展示如何使用useCallback,我们需要查看setAuth 的定义位置(即您渲染AuthContext.Provider 的位置) 你在这里遵循什么官方模式(谁的文档解释你应该这样做)?因为影响是针对组件实例更新时应该发生的副作用。您不应该直接制作其他代码调用效果,该代码应该更新拥有的组件,以便 it 触发适当的副作用。 我没有看到无限循环 - codesandbox.io/s/use-context-infinite-loop-q5xk4。代码与您提供的完全一样。 @Mike'Pomax'Kamermans:你说得对,我可能确实混合了一些不同的文档来源和示例......你的意思是我不应该在 MyComponent 的 useEffect() 中调用 setAuth 吗?跨度> 【参考方案1】:

如果你想在组件挂载时只运行一次 useEffect 调用,我认为你应该保持原样,这样做没有错。但是,如果您想摆脱警告,您应该像您提到的那样将 setAuth 包装在 useCallback 中。

const setAuthCallback = useCallback(setAuth, []);

然后在useEffect中放入你的依赖列表:

useEffect(() => 
    Auth.isCurrentUserAuthenticated()
      .then(user => 
        setAuth(isAuthenticated: true, user);
    )
    .catch(err => console.error(err));
  , [setAuthCallback]);

如果您可以控制 AuthContext Provider,最好将您的 setAuth 函数包装在里面。

OP 编辑​​后: 这很有趣,setAuth 是 useState 中的一个函数,它应该始终是相同的,它不应该导致无限循环,除非我遗漏了一些明显的东西

编辑 2:

好的,我想我知道这个问题。好像在呼唤

setAuth( isAuthenticated: true, user );

正在重新实例化 AuthProvider 组件,该组件重新创建导致无限循环的 setAuth 回调。 转载:https://codesandbox.io/s/heuristic-leftpad-i6tw7?file=/src/App.js:973-1014

在正常情况下,您的示例应该可以正常工作

【讨论】:

是的,我可以控制AuthContext,我会将其添加到问题中...感谢您的回答! 我不得不使用setAuthCallback(isAuthenticated: true, user) 而不是setAuth(isAuthenticated: true, user) in the useEffectconst setAuthCallback = useCallback(setAuth, [setAuth])(带有setAuth 依赖项)以避免额外的警告。现在它可以工作了,没有任何警告。但我更愿意将setAuth 包裹在AuthContext 中的useCallback() 中,如果可能的话,... 我可以向你保证它确实会导致无限循环... :-) 我明白了。但是如何让我的例子在我解释过的情况下发挥作用呢? @Marcos 我认为关键部分是 - 为什么在您的示例中实际发生这种情况?为什么调用 setAuth 时会重新创建(而不是重新渲染)AuthProvider?【参考方案2】:

这是useContext 的默认行为。 如果您通过setAuth 更改上下文值,那么最近的提供程序将使用最新的上下文进行更新,然后您的组件将因此再次更新。

为避免这种重新渲染行为,您需要记住您的组件。

这是官方文档所说的

接受一个上下文对象(从 React.createContext 返回的值) 并返回该上下文的当前上下文值。目前的 上下文值由最近的 value 属性决定 在树中调用组件上方。

当组件上方最近的更新时, 此 Hook 将触发重新渲染,并传递最新的上下文值 到那个 MyContext 提供者。即使祖先使用 React.memo 或 shouldComponentUpdate,重新渲染仍然会从 组件本身使用 useContext。

像这样?

function Button() 
  let appContextValue = useContext(AppContext);
  let theme = appContextValue.theme; // Your "selector"

  return useMemo(() => 
    // The rest of your rendering logic
    return <ExpensiveTree className=theme />;
  , [theme])

【讨论】:

但是您仍然可以将 setAuth 函数包装在提供程序中的 useCallback 中,不是吗? @MistyK:是的,我可以,但我不清楚该怎么做……【参考方案3】:

我终于解决了不在MyComponent中使用useCallback,而是在ContextProvider中:

import React,  useState, useCallback, createContext  from "react";

const initialState =  authorized: false, user: null ;

const AuthContext = createContext(initialState);

const AuthProvider = (props) => 
  const [auth, setAuth] = useState(initialState);
  const setAuthPersistent = useCallback(setAuth, [setAuth]);

  return (
    <AuthContext.Provider value= auth, setAuth: setAuthPersistent >
      props.children
    </AuthContext.Provider>
  )
;

export  AuthProvider, AuthContext ;

我不确定这是不是最好的模式,因为代码不是那么直接和不言自明,但它可以工作,没有无限循环,也没有任何警告......

【讨论】:

真的解决了问题吗?这并没有多大意义——setAuthPersistent 总是等价于 setAuth。它应该会导致您之前遇到的相同问题。 @Mistyk:你是对的!我确实尝试在上下文提供程序中再次简单地使用 setAuth,并且一切正常,没有警告也没有循环......恐怕我无法理解循环的情况发生了什么变化......对不起每个人在这个线程上工作...... :-( 我告诉过你这很可疑 :)

以上是关于如何避免“React Hook useEffect 缺少依赖项”和无限循环的主要内容,如果未能解决你的问题,请参考以下文章

我应该如何测试使用 Typescript 进行 api 调用的 React Hook “useEffect”?

如何修复 React Redux 和 React Hook useEffect 缺少依赖项:'dispatch'

如何解决此警告:“React Hook useEffect 缺少依赖项:'history'”?

如何解决“React Hook useEffect 缺少依赖项。包括它或删除依赖项数组”问题?

如何在 React Native 应用程序中使用 React hook useEffect 为每 5 秒渲染设置间隔?

React Hook useEffect缺少依赖项。包括它们或删除依赖项数组