react-router v6實現動態路由實例
前言
最近在肝一個後臺管理項目,用的是react18 + ts 路由用的是v6,當需要實現根據權限動態加載路由表時,遇到瞭不少問題。
v6相比於v5做瞭一系列改動,通過路由表進行映射就是一個很好的改變(個人認為),但是怎麼實現根據權限動態加載路由表呢?我也是網站上找瞭許多資料發現大部分還是以前版本的動態路由,要是按照現在的路由表來寫肯定是不行的。難不成又要寫成老版本那樣錯綜復雜?隻能自己來手寫一個瞭,如有更好的方法望大佬們不吝賜教。
思路
大致思路就是:先隻在路由表配置默認路由,例如登錄頁面,404頁面。再等待用戶登錄成功後,獲取到用戶權限列表和導航列表,寫一個工具函數遞歸調用得出路由表,在根據關鍵字映射成組件,最後返回得到新的路由表。
流程如下
- 用戶登錄成功
- 獲取用戶權限列表
- 獲取用戶導航菜單列表
- 根據權限和導航生成路由表
紙上談來終覺淺,實際來看看吧。
實現動態路由
router/index.ts 默認路由
import { lazy } from "react"; import { Navigate } from "react-router-dom"; // React 組件懶加載 // 快速導入工具函數 const lazyLoad = (moduleName: string) => { const Module = lazy(() => import(`views/${moduleName}`)); return <Module />; }; // 路由鑒權組件 const Appraisal = ({ children }: any) => { const token = localStorage.getItem("token"); return token ? children : <Navigate to="/login" />; }; interface Router { name?: string; path: string; children?: Array<Router>; element: any; } const routes: Array<Router> = [ { path: "/login", element: lazyLoad("login"), }, { path: "/", element: <Appraisal>{lazyLoad("sand-box")}</Appraisal>, children: [ { path: "", element: <Navigate to="home" />, }, { path: "*", element: lazyLoad("sand-box/nopermission"), }, ], }, { path: "*", element: lazyLoad("not-found"), }, ]; export default routes;
redux login/action.ts
註意帶 //import! 的標識每次導航列表更新時,再觸發路由更新action
handelFilterRouter 就是根據導航菜單列表 和權限列表 得出路由表的
import { INITSIDEMENUS, UPDATUSERS, LOGINOUT, UPDATROUTES } from "./contant"; import { getSideMenus } from "services/home"; import { loginUser } from "services/login"; import { patchRights } from "services/right-list"; import { handleSideMenu } from "@/utils/devUtils"; import { handelFilterRouter } from "@/utils/routersFilter"; import { message } from "antd"; // 獲取導航菜單列表 export const getSideMenusAction = (): any => { return (dispatch: any, state: any) => { getSideMenus().then((res: any) => { const rights = state().login.users.role.rights; const newMenus = handleSideMenu(res, rights); dispatch({ type: INITSIDEMENUS, menus: newMenus }); dispatch(updateRoutesAction()); //import! }); }; }; // 退出登錄 export const loginOutAction = (): any => ({ type: LOGINOUT }); // 更新導航菜單 export const updateMenusAction = (item: any): any => { return (dispatch: any) => { patchRights(item).then((res: any) => { dispatch(getSideMenusAction()); }); }; }; // 路由更新 //import! export const updateRoutesAction = (): any => { return (dispatch: any, state: any) => { const rights = state().login.users.role.rights; const menus = state().login.menus; const routes = handelFilterRouter(rights, menus); //import! dispatch({ type: UPDATROUTES, routes }); }; }; // 登錄 export const loginUserAction = (item: any, navigate: any): any => { return (dispatch: any) => { loginUser(item).then((res: any) => { if (res.length === 0) { message.error("用戶名或密碼錯誤"); } else { localStorage.setItem("token", res[0].username); dispatch({ type: UPDATUSERS, users: res[0] }); dispatch(getSideMenusAction()); navigate("/home"); } }); }; };
utils 工具函數處理
說一說我這裡為什麼要映射element 成對應組件這部操作,原因是我使用瞭redux-persist(redux持久化), 不熟悉這個插件的可以看看我這篇文章:redux-persist若是直接轉換後存入本地再取出來渲染是會有問題的,所以需要先將element保存成映射路徑,然後渲染前再進行一次路徑映射出對應組件。
每個後臺的數據返回格式都不一樣,需要自己去轉換,我這裡的轉換僅供參考。ps:defaulyRoutes和默認router/index.ts導出是一樣的,可以做個小優化,復用起來。
import { lazy } from "react"; import { Navigate } from "react-router-dom"; // 快速導入工具函數 const lazyLoad = (moduleName: string) => { const Module = lazy(() => import(`views/${moduleName}`)); return <Module />; }; const Appraisal = ({ children }: any) => { const token = localStorage.getItem("token"); return token ? children : <Navigate to="/login" />; }; const defaulyRoutes: any = [ { path: "/login", element: lazyLoad("login"), }, { path: "/", element: <Appraisal>{lazyLoad("sand-box")}</Appraisal>, children: [ { path: "", element: <Navigate to="home" />, }, { path: "*", element: lazyLoad("sand-box/nopermission"), }, ], }, { path: "*", element: lazyLoad("not-found"), }, ]; // 權限列表 和 導航菜單 得出路由表 element暫用字符串表示 後面渲染前再映射 export const handelFilterRouter = ( rights: any, menus: any, routes: any = [] ) => { for (const menu of menus) { if (menu.pagepermisson) { let index = rights.findIndex((item: any) => item === menu.key) + 1; if (!menu.children) { if (index) { const obj = { path: menu.key, element: `sand-box${menu.key}`, }; routes.push(obj); } } else { handelFilterRouter(rights, menu.children, routes); } } } return routes; }; // 返回最終路由表 export const handelEnd = (routes: any) => { defaulyRoutes[1].children = [...routes, ...defaulyRoutes[1].children]; return defaulyRoutes; }; // 映射element 成對應組件 export const handelFilterElement = (routes: any) => { return routes.map((route: any) => { route.element = lazyLoad(route.element); return route; }); };
App.tsx
import routes from "./router"; import { useRoutes } from "react-router-dom"; import { shallowEqual, useSelector } from "react-redux"; import { useState, useEffect } from "react"; import { handelFilterElement, handelEnd } from "@/utils/routersFilter"; import { deepCopy } from "@/utils/devUtils"; function App() { console.log("first"); const [rout, setrout] = useState(routes); const { routs } = useSelector( (state: any) => ({ routs: state.login.routes }), shallowEqual ); const element = useRoutes(rout); // 監聽路由表改變重新渲染 useEffect(() => { // deepCopy 深拷貝state數據 不能影響到store裡的數據! // handelFilterElement 映射對應組件 // handelEnd 將路由表嵌入默認路由表得到完整路由表 const end = handelEnd(handelFilterElement(deepCopy(routs))); setrout(end); }, [routs]); return <div className="height-all">{element}</div>; } export default App;
以上就是react-router v6實現動態路由實例的詳細內容,更多關於react-router v6動態路由的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- React-Router(V6)的權限控制實現示例
- React-Router6版本的更新引起的路由用法變化
- 使用React Router v6 添加身份驗證的方法
- react-router v6實現權限管理+自動替換頁面標題的案例
- 使用vue-element-admin框架從後端動態獲取菜單功能的實現