广告位联系
返回顶部
分享到

react-router v6实现动态路由

JavaScript 来源:互联网 作者:佚名 发布时间:2022-08-17 23:12:19 人浏览
摘要

最近在肝一个后台管理项目,用的是react18 + ts 路由用的是v6,当需要实现根据权限动态加载路由表时,遇到了不少问题。 v6相比于v5做了一系列改动,通过路由表进行映射就是一个很好

最近在肝一个后台管理项目,用的是react18 + ts 路由用的是v6,当需要实现根据权限动态加载路由表时,遇到了不少问题。

v6相比于v5做了一系列改动,通过路由表进行映射就是一个很好的改变(个人认为),但是怎么实现根据权限动态加载路由表呢?我也是网站上找了许多资料发现大部分还是以前版本的动态路由,要是按照现在的路由表来写肯定是不行的。难不成又要写成老版本那样错综复杂?只能自己来手写一个了,如有更好的方法望大佬们不吝赐教。

思路

大致思路就是:先只在路由表配置默认路由,例如登录页面,404页面。再等待用户登录成功后,获取到用户权限列表和导航列表,写一个工具函数递归调用得出路由表,在根据关键字映射成组件,最后返回得到新的路由表。

流程如下

  • 用户登录成功
  • 获取用户权限列表
  • 获取用户导航菜单列表
  • 根据权限和导航生成路由表

纸上谈来终觉浅,实际来看看吧。

实现动态路由

router/index.ts 默认路由

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

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 就是根据导航菜单列表 和权限列表 得出路由表的

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

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导出是一样的,可以做个小优化,复用起来。

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

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

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

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;


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://juejin.cn/post/7132393527501127687
相关文章
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计