• 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 ;
    };
    // 路由鉴权组件
    const Appraisal = ({ children }: any) => {
      const token = localStorage.getItem("token");
      return token ? children : ;
    };
    
    interface Router {
      name?: string;
      path: string;
      children?: Array;
      element: any;
    }
    
    const routes: Array = [
      {
        path: "/login",
        element: lazyLoad("login"),
      },
      {
        path: "/",
        element: {lazyLoad("sand-box")},
        children: [
          {
            path: "",
            element: ,
          },
          {
            path: "*",
            element: lazyLoad("sand-box/nopermission"),
          },
        ],
      },
      {
        path: "*",
        element: lazyLoad("not-found"),
      },
    ];
    
    
    export default routes;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    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");
          }
        });
      };
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    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 ;
    };
    const Appraisal = ({ children }: any) => {
      const token = localStorage.getItem("token");
      return token ? children : ;
    };
    
    const defaulyRoutes: any = [
      {
        path: "/login",
        element: lazyLoad("login"),
      },
      {
        path: "/",
        element: {lazyLoad("sand-box")},
        children: [
          {
            path: "",
            element: ,
          },
          {
            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;
      });
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    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 
    {element}
    ; } export default App;
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    End

    核心代码都在这了,如若需要完整代码可以私聊哈。
    一键三连,关注不迷路!
    在这里插入图片描述

  • 相关阅读:
    Anaconda使用指南
    基于遗传算法的水力发电厂的优化(Matlab代码实现)
    短视频被替代的趋势-今抖云创
    victoriaMetrics之byteBuffer
    C++ -- OpenMP 笔记
    MySQL8.0优化 - 锁 - 从对待锁的态度划分:乐观锁、悲观锁
    代理模式——设计模式
    思腾云计算
    Python3学习笔记——第一章:基础入门
    【LeetCode】39、组合总和
  • 原文地址:https://blog.csdn.net/kzj0916/article/details/126369635