React-Router v6 实现登录验证流程
此示例演示了一个包含三个页面的简单登录流程:公共页面、受保护页面和登录页面。 为了查看受保护的页面,你必须先登录。 首先,访问公共页面。 然后,访问受保护的页面。 你尚未登录,因此你将被重定向到登录页面。 登录后,你将被重定向回受保护的页面。
封装 Context 包裹容器
首先封装AuthProvider
组件,利用Context
特性共享那些对于一个组件树而言是“全局”的数据。
全局定义user
、signIn
、signOut
数据和方法,signIn
、signOut
使用了高阶函数,也方便后续扩展和修改。
Context
主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。 如果你只是想避免层层传递一些属性,组件组合(component composition)有时候是一个比Context
更好的解决方案。
import { ReactNode, createContext, useState } from "react";
export interface AuthContextType {
user: any;
signIn: (user: string, callback: VoidFunction) => void;
signOut: (callback: VoidFunction) => void;
}
export let AuthContext = createContext<AuthContextType | null>(null);
const fakeAuthProvider = {
isAuthenticated: false,
signIn(callback: VoidFunction) {
this.isAuthenticated = true;
setTimeout(callback, 100);
},
signOut(callback: VoidFunction) {
this.isAuthenticated = false;
setTimeout(callback, 100);
},
};
const AuthProvider = ({ children }: { children: ReactNode }) => {
const [user, setUser] = useState<any>(null);
let signIn = (newUser: string, callback: VoidFunction) => {
return fakeAuthProvider.signIn(() => {
setUser(newUser);
callback();
});
};
let signOut = (callback: VoidFunction) => {
return fakeAuthProvider.signOut(() => {
setUser(null);
callback();
});
};
return (
<AuthContext.Provider value={{ user, signIn, signOut }}>
{children}
</AuthContext.Provider>
);
};
export default AuthProvider;
封装 Layout 父级容器
Layout
组件主要是针对登录状态进行校验,然后做相应处理。利用react-router
v6中<Outlet />
组件显示嵌套路由,相比于v5版本v6实现嵌套路由更加方便,省略了很多冗余的判断代码。
import { useContext } from "react";
import { useNavigate, Link, Outlet } from "react-router-dom";
import { AuthContext, AuthContextType } from "../AuthProvider";
const useAuth = () => useContext(AuthContext);
const AuthStatus = () => {
let auth = useAuth();
let { user, signOut } = auth as AuthContextType;
let navigate = useNavigate();
if (!user) return <p>没有登录</p>;
return (
<>
<p>你好 {user}! </p>
<button onClick={() => signOut(() => navigate("/"))}>退出</button>
</>
);
};
const Layout = () => {
return (
<div>
<AuthStatus />
<ul>
<li>
<Link to="/">公共页面</Link>
</li>
<li>
<Link to="/protected">受保护页面</Link>
</li>
</ul>
<Outlet />
</div>
);
};
export default Layout;
开发 Login 模块
import { useContext, FormEvent } from "react";
import { useNavigate, useLocation, Location } from "react-router-dom";
import { AuthContext, AuthContextType } from "../AuthProvider";
interface State extends Omit<Location, "state"> {
state: {
from: {
pathname: string;
};
};
}
const useAuth = () => useContext(AuthContext);
const Login = () => {
let auth = useAuth();
let { signIn } = auth as AuthContextType;
const { state } = useLocation() as State;
let from = state.from.pathname || "/";
let navigate = useNavigate();
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
let formData = new FormData(event.currentTarget);
let username = formData.get("username") as string;
signIn(username, () => navigate(from, { replace: true }));
};
return (
<div>
<p>您必须登录才能查看该页面 {from}</p>
<form onSubmit={handleSubmit}>
<label>
用户名: <input name="username" type="text" />
</label>
<button type="submit">登录</button>
</form>
</div>
);
};
export default Login;
开发 Protected 包裹容器
主要就是对登录状态进行校验,成功则渲染子组件,否则跳转回登录页面
import { useContext } from "react";
import { useLocation, Navigate } from "react-router-dom";
import { AuthContext, AuthContextType } from "../AuthProvider";
const useAuth = () => useContext(AuthContext);
const RequireAuth = ({ children }: { children: JSX.Element }) => {
let auth = useAuth();
let { user } = auth as AuthContextType;
let location = useLocation();
if (!user) return <Navigate to="/login" state={{ from: location }} replace />;
return children;
};
export default RequireAuth;
App 入口文件
入口文件没有对路由进行懒加载优化,因为是小应用,所以实际开发还是要考虑性能优化的。
import { Routes, Route } from "react-router-dom";
import AuthProvider from "src/views/AuthProvider";
import Layout from "src/views/auth/layout";
import LoginPage from "src/views/auth/login";
import PublicPage from "src/views/auth/publicPage";
import RequireAuth from "src/views/auth/requireAuth";
import ProtectedPage from "src/views/auth/protectedPage";
const App = () => {
return (
<AuthProvider>
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<PublicPage />} />
<Route path="/login" element={<LoginPage />} />
<Route
path="/protected"
element={
<RequireAuth>
<ProtectedPage />
</RequireAuth>
}
/>
</Route>
</Routes>
</AuthProvider>
);
};
export default App;
参考资料: React Router官方文档
转载自:https://juejin.cn/post/7102713071407366157