路由权限,是webapp或者说后台管理都会需要的业务功能。现在对react-routerv6.x的路由库,封装一个简易的权限路由,实现思路:
后台登录效果
代码实现
思路就是对路由表迭代出来的路由,用一个HOC来进行拦截,在真实进入路由之前提前做一些事情,获取用户信息判断权限,token数据,根据不同的状态来决定渲染各自的页面。
- 项目结构
- 依赖库版本
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^6.28.0",
"react-router-dom": "^6.28.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
- router/routes.js
import { Navigate } from "react-router-dom";
import { lazy } from "react";
import Home from "../pages/Home.jsx";
export default [
{
path: "/",
name: "index",
component: () => <Navigate to="/home" replace />,
},
{
path: "/home",
name: "home",
component: Home,
meta: {
title: "Home",
description: "Home page",
},
},
{
path: "/boss",
name: "boss",
component: lazy(() => import("../pages/Boss.jsx")),
},
{
path: "/detail/:id",
name: "detail-detail",
component: lazy(() => import("../pages/Detail.jsx")),
},
{
path: "/user",
name: "User",
component: lazy(() => import("../pages/User.jsx")),
},
{
path: "/login",
name: "Login",
component: lazy(() => import("../pages/Login.jsx")),
},
{
path: "*",
name: "not-found",
component: lazy(() => import("../pages/404.jsx")),
},
];
- router/index.js
import routesList from "./routes";
import {
Routes,
Route,
useLocation,
useNavigate,
useParams,
useSearchParams,
NavLink,
} from "react-router-dom";
import { Suspense, useEffect } from "react";
const whiteLists = ["/login"];
function Element({ component: Component, ...rest }) {
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
if (
!whiteLists.includes(location.pathname) &&
!localStorage.getItem("token")
) {
navigate("/login");
} else if (
location.pathname === "/login" &&
localStorage.getItem("token")
) {
navigate("/");
}
return () => {};
}, [navigate]);
return <Component {...rest}></Component>;
}
function createRouter() {
return (
<>
{routesList.map((route, index) => {
return (
<Route
key={route.name}
path={route.path}
element={<Element {...route} />}
/>
);
})}
</>
);
}
export default function RouterView() {
return (
<>
<Suspense fallback={<div>Loading...</div>}>
<Routes>{createRouter()}</Routes>
</Suspense>
<div style={{ textAlign: "center" }}>
<NavLink to="/"> home </NavLink> | <NavLink to="/boss">Boss</NavLink> |{" "}
<NavLink to="/user">User</NavLink>
</div>
</>
);
}
export function WithRouter(Component) {
return function Hoc(props) {
// 提前获取路由信息,传递给组件
const location = useLocation(),
navigate = useNavigate(),
params = useParams(),
[usp] = useSearchParams();
const pathObj = { location, navigate, params, usp };
return <Component {...props} {...pathObj} />;
};
}
- pages/Login.jsx
import { useNavigate } from "react-router-dom";
function Login() {
const navigate = useNavigate();
function handleSubmit() {
window.localStorage.setItem("token", "admin");
navigate("/");
}
return (
<div
style={{ display: "flex", justifyContent: "center", margin: "20px auto" }}
>
<form>
<input type="text" name="username" placeholder="Username" /> <br />
<br />
<input type="password" name="password" placeholder="Password" /> <br />
<br />
<button onClick={handleSubmit}>Login</button>
</form>
</div>
);
}
export default Login;
- pages/User.js
import { useNavigate } from "react-router-dom";
function User() {
const navigate = useNavigate();
function handleClick() {
localStorage.removeItem("token");
navigate("/login");
}
return (
<div>
<h1>User Page</h1>
<p>This is the user page.</p>
<div>
<button onClick={handleClick}>退出登录</button>
</div>
</div>
);
}
export default User;
体验优化
上面的效果会存在比较明显的闪烁,优化代码如下
function Element({ component: Component, ...rest }) {
const location = useLocation();
const navigate = useNavigate();
if (
!whiteLists.includes(location.pathname) &&
!localStorage.getItem("token")
) {
navigate("/login");
} else if (location.pathname === "/login" && localStorage.getItem("token")) {
navigate("/");
}
return <Component {...rest}></Component>;
}
对HOC组件的渲染逻辑进行改写,页面切换的时候体验好了很多。
总结
这只是简易的封装了下路由结构,没考虑二级路由或者多级的情况,思路就是这么思路,可以自行去实现下。