我写了一个像这样的 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()
答案 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 决定
当组件上方最近的
喜欢吗?
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 };
我不确定这是最好的模式,因为代码不是那么简单和不言自明,但它可以工作,没有无限循环也没有任何警告......