【问题标题】:How to avoid "React Hook useEffect has a missing dependency" and infinite loops如何避免“React Hook useEffect 缺少依赖项”和无限循环
【发布时间】: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 吗?跨度>

标签: javascript reactjs react-hooks use-effect use-context


【解决方案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,并且一切正常,没有警告也没有循环......恐怕我无法理解循环的情况发生了什么变化......对不起每个人在这个线程上工作...... :-(
  • 我告诉过你这很可疑 :)
猜你喜欢
  • 2021-02-23
  • 2019-10-24
  • 2020-10-26
  • 2020-03-07
  • 2020-02-25
  • 2020-06-11
  • 2020-03-30
  • 2020-12-29
  • 2019-09-20
相关资源
最近更新 更多