react-router?v6實(shí)現(xiàn)動(dòng)態(tài)路由實(shí)例
前言
最近在肝一個(gè)后臺(tái)管理項(xiàng)目,用的是react18 + ts 路由用的是v6,當(dāng)需要實(shí)現(xiàn)根據(jù)權(quán)限動(dòng)態(tài)加載路由表時(shí),遇到了不少問(wèn)題。
v6相比于v5做了一系列改動(dòng),通過(guò)路由表進(jìn)行映射就是一個(gè)很好的改變(個(gè)人認(rèn)為),但是怎么實(shí)現(xiàn)根據(jù)權(quán)限動(dòng)態(tài)加載路由表呢?我也是網(wǎng)站上找了許多資料發(fā)現(xiàn)大部分還是以前版本的動(dòng)態(tài)路由,要是按照現(xiàn)在的路由表來(lái)寫(xiě)肯定是不行的。難不成又要寫(xiě)成老版本那樣錯(cuò)綜復(fù)雜?只能自己來(lái)手寫(xiě)一個(gè)了,如有更好的方法望大佬們不吝賜教。
思路
大致思路就是:先只在路由表配置默認(rèn)路由,例如登錄頁(yè)面,404頁(yè)面。再等待用戶(hù)登錄成功后,獲取到用戶(hù)權(quán)限列表和導(dǎo)航列表,寫(xiě)一個(gè)工具函數(shù)遞歸調(diào)用得出路由表,在根據(jù)關(guān)鍵字映射成組件,最后返回得到新的路由表。
流程如下
- 用戶(hù)登錄成功
- 獲取用戶(hù)權(quán)限列表
- 獲取用戶(hù)導(dǎo)航菜單列表
- 根據(jù)權(quán)限和導(dǎo)航生成路由表
紙上談來(lái)終覺(jué)淺,實(shí)際來(lái)看看吧。
實(shí)現(xiàn)動(dòng)態(tài)路由
router/index.ts 默認(rèn)路由
import { lazy } from "react"; import { Navigate } from "react-router-dom"; // React 組件懶加載 // 快速導(dǎo)入工具函數(shù) const lazyLoad = (moduleName: string) => { const Module = lazy(() => import(`views/${moduleName}`)); return <Module />; }; // 路由鑒權(quán)組件 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! 的標(biāo)識(shí)每次導(dǎo)航列表更新時(shí),再觸發(fā)路由更新action
handelFilterRouter 就是根據(jù)導(dǎo)航菜單列表 和權(quán)限列表 得出路由表的
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"; // 獲取導(dǎo)航菜單列表 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 }); // 更新導(dǎo)航菜單 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("用戶(hù)名或密碼錯(cuò)誤"); } else { localStorage.setItem("token", res[0].username); dispatch({ type: UPDATUSERS, users: res[0] }); dispatch(getSideMenusAction()); navigate("/home"); } }); }; };
utils 工具函數(shù)處理
說(shuō)一說(shuō)我這里為什么要映射element 成對(duì)應(yīng)組件這部操作,原因是我使用了redux-persist(redux持久化), 不熟悉這個(gè)插件的可以看看我這篇文章:redux-persist若是直接轉(zhuǎn)換后存入本地再取出來(lái)渲染是會(huì)有問(wèn)題的,所以需要先將element保存成映射路徑,然后渲染前再進(jìn)行一次路徑映射出對(duì)應(yīng)組件。
每個(gè)后臺(tái)的數(shù)據(jù)返回格式都不一樣,需要自己去轉(zhuǎn)換,我這里的轉(zhuǎn)換僅供參考。ps:defaulyRoutes和默認(rèn)router/index.ts導(dǎo)出是一樣的,可以做個(gè)小優(yōu)化,復(fù)用起來(lái)。
import { lazy } from "react"; import { Navigate } from "react-router-dom"; // 快速導(dǎo)入工具函數(shù) 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"), }, ]; // 權(quán)限列表 和 導(dǎo)航菜單 得出路由表 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 成對(duì)應(yīng)組件 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); // 監(jiān)聽(tīng)路由表改變重新渲染 useEffect(() => { // deepCopy 深拷貝state數(shù)據(jù) 不能影響到store里的數(shù)據(jù)! // handelFilterElement 映射對(duì)應(yīng)組件 // handelEnd 將路由表嵌入默認(rèn)路由表得到完整路由表 const end = handelEnd(handelFilterElement(deepCopy(routs))); setrout(end); }, [routs]); return <div className="height-all">{element}</div>; } export default App;
以上就是react-router v6實(shí)現(xiàn)動(dòng)態(tài)路由實(shí)例的詳細(xì)內(nèi)容,更多關(guān)于react-router v6動(dòng)態(tài)路由的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React觸發(fā)render的實(shí)現(xiàn)方法
這篇文章主要介紹了React觸發(fā)render的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10如何用react優(yōu)雅的書(shū)寫(xiě)CSS
這篇文章主要介紹了如何用react優(yōu)雅的書(shū)寫(xiě)CSS,幫助大家更好的理解和學(xué)習(xí)使用react,感興趣的朋友可以了解下2021-04-04React中獲取數(shù)據(jù)的3種方法及優(yōu)缺點(diǎn)
這篇文章主要介紹了React中獲取數(shù)據(jù)的3種方法及優(yōu)缺點(diǎn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02react 不用插件實(shí)現(xiàn)數(shù)字滾動(dòng)的效果示例
這篇文章主要介紹了react 不用插件實(shí)現(xiàn)數(shù)字滾動(dòng)的效果示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04