如何避免“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()
来避免每次都重新实例化该函数,但我真的不明白如何将useCallback
与useContext()
中的函数一起使用
【问题讨论】:
你能显示 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 useEffect
和const 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 缺少依赖项。包括它或删除依赖项数组”问题?