我创建了一个受保护的路由,该路由旨在调用一个函数getAuth()
,该函数为:
- 向服务器发送JWT,以便对用户进行身份验证
- 从JWT响应中接收到用户信息后,更新全局上下文以匹配
此受保护路由的目的是通过在每个页面后重置全局上下文来对用户进行身份验证,然后在用户无法进行身份验证时使用该全局上下文将其重定向到正确的页面。
如果我不使用getAuth()
函数,而只依赖全局上下文,那么当用户刷新页面时,全局上下文就会丢失。
我的UserProtected路由代码如下:
//UserProtecterRoute.js
import { useMemo, useRef, useState } from "react";
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../context/AuthContext";
export default function UserProtectedRoute() {
const getAuth = useAuth().GetAuth;
const useAuthState = useAuth().authState;
var authorized = useRef(false);
useMemo(() => {
if (localStorage.getItem("token")) {
getAuth();
if (useAuthState.username) {
authorized.current = true;
}
}
}, []);
if (authorized.current) {
return <Outlet />;
} else {
return <Navigate to="/Login"></Navigate>;
}
}
全局授权上下文的代码如下:
//AuthContext.js
import React, { useContext, useState } from "react";
import * as ReactDOM from "react-dom";
import axios from "axios";
const AuthContext = React.createContext();
export function useAuth() {
// returns a function which once run returns an object with the values you
// placed in it. In this case it returns {authState,setAuthState,GetAuth}
// you can call this object.prop to use the prop
return useContext(AuthContext);
}
export function AuthProvider({ children }) {
const [authState, setAuthState] = useState({
id: 0,
username: "",
role: "user",
});
async function GetAuth() {
if (localStorage.getItem("token")) {
await axios
.get("http://localhost:3001/Auth", {
headers: {
accessToken: localStorage.getItem("token"),
},
})
.then((res) => {
setAuthState((prev) => ({
...prev,
id: res.data.id,
username: res.data.user,
role: res.data.role,
}));
})
.catch((error) => {
console.log(error);
});
}
}
return (
<AuthContext.Provider value={{ authState, setAuthState, GetAuth }}>
{children}
</AuthContext.Provider>
);
}
当我运行代码进行调试时,它遵循以下顺序:
调用- getAuth((
- axios get请求在AuthContext.js中发送
- useMemo中的
if(useAuthState.username)
语句已运行,但useAuthState.username未定义 - UserProtected路由中的其余代码正在运行,但是,由于AuthContext尚未更新,它重定向到错误的路由
- AuthContext的then((运行(本应设置全局上下文,但现在为时已晚(
- AuthContext的axios调用第二次运行
- AuthContext的then((再次运行
我认为这个问题与promise有关,我尝试将if(localStorage.getItem('token'))
封装在异步函数中,并从getAuth中放入wait,但也没有成功。我也怀疑setState的实现可能与此有关,但我在React和Promises方面的经验让我自己无法理解。
如果有人能指导我做错事,我将不胜感激
我会改为执行以下操作。。。
- 在装载时触发
AuthProvider
组件内的get-auth效果。这样,您就不必将其委托给其他组件 - 在上下文中添加一个额外的状态,让消费者知道它正在加载
- 依赖上下文状态来呈现经过身份验证的组件。不需要额外的
ref
在您的上下文提供程序中。。。
// This could be in a separate, testable module
const fetchAuth = async (accessToken) => {
const {
data: { id, user, role },
} = await axios.get("http://localhost:3001/Auth", {
headers: { accessToken },
});
return { id, username: user, role };
};
export function AuthProvider({ children }) {
const [authState, setAuthState] = useState({
id: 0,
username: "",
role: "user",
});
// Loading state
const [isLoading, setIsLoading] = useState(false);
// Helper memo
const isAuthenticated = useMemo(() => !!authState.username, [authState]);
const getAuth = async () => {
setIsLoading(true);
const token = localStorage.getItem("token");
try {
if (token) {
setAuthState(await fetchAuth(token));
}
} catch (err) {
console.error(err.toJSON());
} finally {
setIsLoading(false);
}
};
// Call getAuth on mount
useEffect(() => {
getAuth();
}, []);
return (
<AuthContext.Provider
value={{ authState, getAuth, isAuthenticated, isLoading }}
>
{children}
</AuthContext.Provider>
);
}
在你的组件中。。。
//UserProtecterRoute.js
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../context/AuthContext";
export default function UserProtectedRoute() {
const { isAuthenticated, isLoading } = useAuth();
if (isLoading) {
// just an example
return <p>Loading...</p>;
}
if (isAuthenticated) {
return <Outlet />;
}
return <Navigate to="/Login"></Navigate>;
}