React Router V6項目中的路由鑑權封裝實踐(Hooks)
1. 前言
1.1 路由封裝的好處
- 路由鑑權集中管理: 封裝路由組件允許你集中管理路由鑑權邏輯。這意味着在一個地方處理用户是否有權限訪問某個路由,而不是在每個頁面或組件中重複相同的鑑權邏輯。這有助於保持一致性,並簡化了對路由鑑權的維護和更新。
- 提高代碼複用性: 封裝路由組件可以促進代碼的複用。你可以將通用的路由配置、鑑權邏輯或其他功能抽象為可複用的組件,以便在整個應用程序中多次使用。這降低了重複編寫相似代碼的需求,提高了代碼複用性。
- 易於擴展: 當項目需求變化時,封裝的路由組件使得擴展和調整路由配置變得更加容易。你可以輕鬆地添加新的路由或更改現有路由的配置,而不會影響到整個應用程序的其他部分。
- 更清晰的項目結構: 路由組件的再封裝可以幫助建立清晰的項目結構。通過將路由相關的代碼放在專用的文件或文件夾中,項目的結構更容易理解和導航,減少了代碼文件的混雜性。
1.2 整體項目結構
- src
- layout
- index.ts # UI主框架(鑑權之後才能進的)
- tools
- auth.ts # 權限相關工具文件
- router
- router.tsx # 路由組件註冊
- routerMap.tsx # 路由表構建
- privateRouter.ts # 權限路由組件
- router.ts # 路由組件註冊
- pages #(下面都是隨便弄的,要對自己的需求)
- community.tsx # 社區壓面
- login.tsx # 登錄界面
- user.ts # 用户界面
- book.ts # 書籍列表界面
2. 前期準備工作
2.1 安裝依賴
pnpm add antd --save # 因為是一個小案例,所以做了基礎的UI開發
pnpm add react-router-dom --save #(現在默認是V6版本的路由)
2.2 編寫工具文件
/**
* 設置token
* @param token
* @returns
*/
export const setToken = (token: string) =>
window.localStorage.setItem("auth_token", token);
/**
* 獲取token
* @returns
*/
export const getToken = () => window.localStorage.getItem("auth_token");
/**
* 獲取token
* @returns
*/
export const clearToken = () => window.localStorage.removeItem("auth_token");
2.3 編寫具體頁面組件
僅僅以社區列表這個組件為例,其實就是每個具體頁面準備好
import React from "react";
export default function Community() {
return <div>社區列表界面</div>;
}
3. 路由組件的開發
3.1 配置項目路由的根組件
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import { BrowserRouter } from "react-router-dom";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
//這裏配置的是BrowserRouter,根據需要,可選擇這個或者HashRouter,兩者差別這裏就略過了,可以看看router v6基礎篇或其他文章
);
3.2 守衞路由的編寫
其實就是做了一個基本的鑑權與過期處理,自己項目如果有更多的需求,就在try裏面加就可以了
import { message } from "antd";
import { ReactElement, useEffect } from "react";
import { getToken } from "../tools/auth";
import { useNavigate } from "react-router-dom";
interface Props {
children: ReactElement;
}
const PrivateRoute = ({ children }: Props) => {
const navigator = useNavigate();
// 對比時間戳是否超過48小時
function isPast48Hours(timestamp: number): boolean {
// 獲取當前時間戳
const currentTimestamp = Math.floor(Date.now() / 1000);
// 計算時間差,單位為秒
const timeDifference = currentTimestamp - timestamp;
// 定義48小時的秒數
const hours48InSeconds = 48 * 60 * 60;
// 判斷時間差是否超過48小時
return timeDifference > hours48InSeconds;
}
useEffect(() => {
try {
const token: any = getToken();
const tokenObj = JSON.parse(token);
if (tokenObj === null || isPast48Hours(tokenObj.expired)) {
message.warning("token過期,請重新登錄");
navigator(`/login`);
}
} catch (error) {
message.warning("token過期,請重新登錄");
navigator(`/login`);
}
}, []);
return <>{children}</>;
};
export default PrivateRoute;
3.3 路由映射表的編寫
這裏沒有直接用<Route/>組件愛你包裹,而是先用js對象形式維護了一套路由表數據,方便其他諸如:菜單/目錄等組件的複用
import { Navigate } from "react-router-dom";
import Login from "../pages/login";
import User from "../pages/User";
import Community from "../pages/Community";
import Book from "../pages/Book";
import Layout from "../layout";
import PrivateRoute from "./privateRoute";
export const routerMap = [
{
path: "/login",
element: <Login />,
},
{
path: "/",
element: (
<PrivateRoute>
<Layout />
</PrivateRoute>
),
children: [
{
path: "/user",
element: <User />,
},
{
path: "/community",
element: <Community />,
},
{
path: "/book",
element: <Book />,
},
],
},
{
path: "*",
element: <Navigate to="/login" />,
}, //其他沒有被註冊過的路徑統一重定位到login
];
3.4 路由註冊的編寫
其實就是將原先的路由表數據註冊為路由組件
import { useRoutes } from "react-router-dom";
import { routerMap } from "./routerMap";
function Router() {
const routerTab = useRoutes(routerMap); //註冊前端路由表
return <div>{routerTab}</div>;
}
export default Router;
3.5 路由渲染
import Router from "./router/router";
function App() {
return (
<>
<Router />
</>
);
}
export default App;
4. 組件內應用
4.1 Layout組件應用測試
Layout佈局組件,一個簡單的小Demo來測試路由正確性,他會被權限組件<PrivateRoute/>包裹,受到保護
import { Tabs, TabsProps } from "antd";
import React from "react";
import { Outlet, useNavigate } from "react-router-dom";
import { clearToken } from "../tools/auth";
export default function Layout() {
const navigator = useNavigate();
const items: TabsProps["items"] = [
{
key: "book",
label: "書籍列表頁面",
},
{
key: "community",
label: "社區列表頁面",
},
{
key: "user",
label: "用户列表頁面",
},
{
key: "login",
label: "退出登錄",
},
];
const handleChangeRoute = (value: string) => {
if (value === "login") {
clearToken();
}
navigator(`/${value}`);
};
return (
<>
<div style={{ maxWidth: "500px", margin: "0 auto" }}>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Tabs
defaultActiveKey="community"
size={"large"}
items={items}
onChange={(value) => {
handleChangeRoute(value);
}}
/>
</div>
<div
style={{
marginTop: "50px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Outlet />
</div>
</div>
</>
);
}
4.2 登錄組件的應用測試
Login登錄組件,一個簡單的小Demo來測試路由正確性,他不會被權限組件<PrivateRoute/>包裹,可以隨意進入
import { Button } from "antd";
import React from "react";
import { setToken } from "../../tools/auth";
import { useNavigate } from "react-router-dom";
export default function Login() {
const navigator = useNavigate();
const handleLogin = () => {
const preToken: object = {
token: "hjsdbvfjhysebfjkd762354",
expired: Date.now(),
};
console.log(JSON.stringify(preToken));
setToken(JSON.stringify(preToken)); //模擬設置token
navigator(`/`);
};
const handleRush = () => {
navigator(`/`); //模擬強行進入主頁
};
return (
<div
style={{
marginTop: "30px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Button
type={"primary"}
onClick={() => {
handleLogin();
}}
style={{ marginRight: "15px" }}
>
登錄進入系統主頁
</Button>
<Button
type={"primary"}
onClick={() => {
handleRush();
}}
>
強行進入系統主頁
</Button>
</div>
);
}
5. 總結
本實踐沒有過多的文本描述,多在代碼中的註釋。但通過此個實踐瞭解學習之後,應該可以較好的掌握在的React Hooks項目中應用Router V6封裝整個項目的路由系統,能夠真正實現一次封裝,多處收益
相關的配套實踐Demo會上傳Github開源
項目鏈接:React Router V6項目中的路由鑑權封裝實踐(Hooks)