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

时间:2021-07-04 17:29:23

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

我写了一个像这样的 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() 来避免每次都重新实例化该函数,但我真的无法理解如何将 useCallback 与来自 useContext()

的函数一起使用

3 个答案:

答案 0 :(得分:2)

如果你想在组件挂载时只运行一次 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

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

答案 1 :(得分:1)

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

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

官方文档是这么说的

<块引用>

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

当组件上方最近的 更新时, 此 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])
}

答案 2 :(得分:0)

我终于解决了不是在 useCallback 中使用 MyComponent,而是在 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 };

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