场景
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,渲染路由
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>;
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()
})
}
}
}
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完成登录授权
import React from 'react'
import { useNavigate } from "react-router-dom";
import useAuth from './hooks/useAuth';
const Login = () => {
const navigate = useNavigate();
const { login } = useAuth();
const handleLogin = () => {
login().then(() => {
navigate("/dashboard");
});
};
return (
<div>
<h1>Login</h1>
<button onClick={handleLogin}>Log in</button>
</div>
)
}
export default Login
接下来Nav.jsx里面添加注销的功能
import { useNavigate } from "react-router-dom";
import useAuth from "./useAuth";
const Nav = () => {
const { authed, logout } = useAuth();
const navigate = useNavigate();
const handleLogout = () => {
logout();
navigate("/");
};
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的样子
...
<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>
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
) : (
<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();
const handleLogin = () => {
login().then(() => {
navigate(state?.path || "/dashboard");
});
};
return (
<div>
<h1>Login</h1>
<button onClick={handleLogin}>Log in</button>
</div>
)
}
export default Login
完毕
|