react-router-dom6实现私密路由
场景
react项目里面有些时候部分页面需要登陆或者部分权限才能访问 这个时候需要私有路由
在创建私有路由之前,需要一种方法来确定用户是否被认证。这里说的是React Router保护路由的方法,而不是关于认证,所以使用一个假的useAuth Hook来确定我们用户的认证 "状态"。
创建文件APP.jsx, hooks/useAuth.jsx, Login.jsx, Nav.jsx
/pricing和/login路径是公开的,/dashboard和/settings路线将是私有的。现在,先是像普通路由一样渲染它们。
创建Login.jsx和Nav.jsx,vscode里快捷键rafce快速生成登录和退出
创建App.jsx,渲染路由
/**
* @file App.jsx
* @description 创建几个组件用于路由使用
* 主页、定价、仪表板、设置和登录
*/
import { Routes, Route } from 'react-router-dom';
import Nav from './Nav';
import Login from './Login';
const Home = () => <h1>Home (Public)</h1>;
const Pricing = () => <h1>Pricing (Public)</h1>;
const Dashboard = () => <h1>Dashboard (Private)</h1>;
const Settings = () => <h1>Settings (Private)</h1>;
/**
* @description /, /pricing, /login routes是共有
* /dashboard, /settings route是私有
*/
export default function App() {
return (
<div>
<Nav />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/pricing" element={<Pricing />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/login" element={<Login />} />
</Routes>
</div>
);
}
在创建hooks/useAuth模拟确定我们用户的认证 "状态"。
有了这个useAuth就知道用户是否被授权、登录或注销 useAuth Hook可以有很多不同的工作方式。 也许它向API端点发出HTTP请求,以验证一个cookie。或者它可以解码存储在浏览器本地存储器中的JWT令牌。或者你可以使用第三方认证解决方案 总之:在任何情况下,目标都是一样的:找出用户当前是否已被认证。
import React from "react";
import { useContext, useState } from 'react'
const authContext = React.createContext()
function useAuth() {
const [authed,setAuthed] = useState() //状态
return {
//认证状态
authed,
//登录
login() {
return new Promise ((res) => {
setAuthed(true)
res()
})
},
//退出
logout() {
return new Promise((res)=>{
setAuthed(false)
res()
})
}
}
}
//用context存储auth状态来进行传递
export function AuthProvider( {children} ) {
const auth = useAuth()
return <authContext.Provider value={auth}>{children}</authContext.Provider>
}
export default function AuthConsumer() {
const { auth } = useContext(authContext);
return auth
}
接下来进入Login.jsx完成登录授权
/**
* @todo 现在开始做一些授权工作 首先完善登录组件。这个组件的目标自然是允许用户登录
*/
import React from 'react'
import { useNavigate } from "react-router-dom";
import useAuth from './hooks/useAuth';
/**
*当用户点击按钮时,调用login(从useAuth Hook获得),然后一旦他们登录,使用navigate,跳转到/dashboard。
*/
const Login = () => {
const navigate = useNavigate();
const { login } = useAuth();
/**
*@function
*@description 登陆的同时authed状态为true并跳转/dashboard
*/
const handleLogin = () => {
login().then(() => {
navigate("/dashboard");
});
};
return (
<div>
<h1>Login</h1>
<button onClick={handleLogin}>Log in</button>
</div>
)
}
export default Login
接下来Nav.jsx里面添加注销的功能
//接下来,让我们添加注销的功能。同样,我们已经有了来自useAuth Hook的注销方法,所以这也应该是简单地添加一些用户界面。所有的改变都将发生在我们的导航组件上。
import { useNavigate } from "react-router-dom";
import useAuth from "./useAuth";
const Nav = () => {
const { authed, logout } = useAuth();
const navigate = useNavigate();
//点击退出authed为false
const handleLogout = () => {
logout();
navigate("/");
};
//authed 为true则有注销登录按钮
return (
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/pricing">Pricing</Link>
</li>
</ul>
{authed && <button onClick={handleLogout}>Logout</button>}
</nav>
);
}
export default Nav
进入App.jsx中让/dashboard和/settings成为私有
在深入实施之前,先提出最终的API可能是什么样子的。对于每条我们希望是私有的路由,不给它的Routes element我们希望它直接呈现的组件,而是把它包在一个新的组件中,言简意赅的设置成RequireAuth。
最终Api的样子
/**
* @description /, /pricing, /login routes是共有
* /dashboard, /settings route是私有
*/
...
<Routes>
<Route path="/" element={<Home />} />
<Route path="/pricing" element={<Pricing />} />
<Route
path="/dashboard"
element={
<RequireAuth>
<Dashboard />
</RequireAuth>
}
/>
<Route
path="/settings"
element={
<RequireAuth>
<Settings />
</RequireAuth>
}
/>
<Route path="/login" element={<Login />} />
</Routes>
...
可以想到RequireAuth目的是让/dashboard和/setting成为私密路由,所以它的逻辑主要是实现两件事情。
首先,它的唯一api是一个children元素。
第二,如果用户通过了认证,它应该呈现那个children元素,如果没有,它应该把用户重定向到他们可以认证的页面
使用我们先前的useAuth Hook来建立<RequireAuth>
/**
* 用它包裹想要验证的路由,此时当一个没有经过认证的用户试图进入/dashboard或/settings时
* 他们会被重定向到/login。然后一旦他们登录,我们就把他们重定向到/dashboard。
*reactrouterdom6中一个<Navigate>元素在渲染时改变当前位置,它是一个围绕useNavigate的组件包
*装,并接受所有与props相同的参数。他替代了5版本中Switch中的<Redirect>
*/
const RequireAuth = ({ children }) => {
const { authed } = useAuth();
return authed === true ? children : <Navigate to="/login" replace />;
}
基本的功能就实现了
补充优化<RequireAuth/>
上面的代码总是将用户重定向到/dashboard,开发中不应该这样子,而应该将他们重定向到他们最初试图访问的路线。例如,如果他们试图访问/settings但没有登录,在我们重定向他们并且他们登录后,我们应该把他们带回/settings,而不是dashboard。 于是要用到useLocation这个钩子返回当前的位置对象,同时 reactrouterdom6中
<Navigate>
组件在渲染时改变当前位置,有两个参数replace和state
const RequireAuth = ({ children })=> {
const { authed } = useAuth();
const location = useLocation();
return authed === true ? (
children
) : (
//使用location.state来保留之前的位置,这样你就可以在用户认证后把他们送到那里。
//replace: true 来替换历史栈中的/login路由,这样用户在登录后点击返回按钮时就不会返回到登录页面。
<Navigate to="/login" replace state={{ path: location.pathname }} />
)
}
最后,完善Login组件
目的:在用户认证后,如果原路径存在,我们会将用户重定向到原路径,如果不存在,我们会将他们带到/dashboard。 可以使用React Router的useLocation Hook来获得对location.state的访问,其中会有我们的path属性。
import { useNavigate,useLocation } from "react-router-dom";
import useAuth from './hooks/useAuth';
const Login = () => {
const navigate = useNavigate();
const { login } = useAuth();
const { state } = useLocation();
/**
*@function
*@description 登陆跳转
*/
const handleLogin = () => {
login().then(() => {
//如果原路径存在,会将用户重定向到原路径,如果不存在,我们会将他们带到/dashboard
navigate(state?.path || "/dashboard");
});
};
return (
<div>
<h1>Login</h1>
<button onClick={handleLogin}>Log in</button>
</div>
)
}
export default Login
完毕
转载自:https://juejin.cn/post/7084078112165593118