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动态路由的资料请关注编程宝库其它相关文章!

小伙伴们知道 TienChin 项目前端用的是 Vue3,当我们把 Vue3 官网刷了一遍之后回来看 TienChin 项目的前端,发现还是有很多不太一样的地方,今天松哥就来和大家捋一捋 Vue3 中几个好 ...