react-router v6 路由统一管理及路由拦截方案

  • 2022-03-11 更新:
    升级插件至1.1.3版本,支持 TypeScript。
  • 2022-03-02 更新:
    修复项目打包后可能无法加载路由的问题;升级插件至1.0.0版本,该版本使用方式上有差别。
  • 2022-02-22 更新:
    重构代码组织,插件化处理(插件传送门

实现的功能:

  • 全局路由统一管理,支持便捷配置路由重定向、路由懒加载、自定义meta字段等。
  • 全局路由拦截,支持读取路由meta配置,支持拦截跳转其他路由等。
  • 同时也支持嵌套路由、动态路由参数等官方路由原生支持的配置方式。

下面分享具体方案。

(没耐心看实现原理的可直接跳到目录:四、使用插件

一、react-router v6

"react": "^17.0.2",
"react-router-dom": "^6.2.1",
"prop-types": "^15.8.1",

v6版本目前网上的文章寥寥无几,实现过程基本靠自己摸索。

  • 官方文档:https://github.com/remix-run/react-router/blob/main/docs/api.md
  • 目前官方文档只有英文版,升级指南啥的网上能搜到中文的文章。
  • 这里只提一下新增的一个api:useRoutes
    useRoutes可以读取一个路由配置数组,生成相应的路由组件列表,类似以前的react-router-config插件的功能,那么路由统一管理的实现用这个api就简单多了。

二、路由统一管理

实现的就是路由集中在一个文件里通过数组统一管理配置。
(这里仅介绍路由统一管理的配置方式示例,非完整代码)

1、路由配置文件

项目src/router/index.js里填写路由配置:

import Index from '@/views/index/index'
import Login from '@/views/login/index'
import Page404 from '@/views/test/page404'

const routes = [
  {
    
    
    path: '/index',
    element: <Index />,
  },
  {
    
    
    path: '/login',
    element: <Login />,
  },
  {
    
    
    path: '*',
    element: <Page404 />,
  },
]

export {
    
     
  routes
}
  • 这里仅作为路由统一管理配置的参考,后面配置路由拦截时会做修改。

2、引入路由

引用路由并渲染的核心是利用react-router v6 的官方api:useRoutes

(1)项目入口文件src/index.js里引入router组件:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import {
    
     BrowserRouter } from 'react-router-dom'

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById('root')
)
  • 路由 browser / history 模式使用BrowserRouter,hash 模式使用HashRouter
  • 如果项目部署在服务器域名子目录下,就给BrowserRouter配置basename属性。

(2)项目入口组件src/App.jsx里引入routes配置:

import {
    
     useRoutes } from 'react-router-dom'
import {
    
     routes } from '@/router'

function App () {
    
    
  const elements = useRoutes(routes)
  return elements
}

export default App
  • useRoutes只能作用于router context中,所以useRoutes需要写在一个子组件里被BrowserRouter引用。

三、全局路由拦截

实现路由全局拦截,来自定义一些判断处理。
实现思路就是控制路由配置的element属性。
(这里仅贴出部分最初版本的核心部分代码,至于使用方式和最新完整代码,可直接跳到下面的:“四、使用插件”)

1、路由及拦截函数配置

src/router/index.js

// 全局路由配置
const routes = [
  {
    
    
    path: '/',
    redirect: '/index',
  },
  {
    
    
    path: '/index',
    component: () => import(/* webpackChunkName: "index" */ '@/views/index/index'),
    meta: {
    
    
      title: '首页',
      needLogin: true,
    },
  },
  {
    
    
    path: '/login',
    component: () => import(/* webpackChunkName: "login" */ '@/views/login/index'), 
    meta: {
    
    
      title: '登录',
    },
  },
  {
    
    
    path: '*',
    component: () => import(/* webpackChunkName: "404" */ '@/views/test/page404'), 
    meta: {
    
    
      title: '404',
    },
  },
]

/**
 * @description: 全局路由拦截
 * @param {string} pathname 当前路由路径
 * @param {object} meta 当前路由自定义meta字段
 * @return {string} 需要跳转到其他页时,就返回一个该页的path路径,或返回resolve该路径的promise对象
 */
const onRouteBefore = ({
    
     pathname, meta }) => {
    
    
  // 动态修改页面title
  if (meta.title !== undefined) {
    
    
    document.title = meta.title
  }
  // 判断未登录跳转登录页
  if (meta.needLogin) {
    
    
    if (!isLogin) {
    
    
      return '/login'
    }
  }
}

export {
    
    
  routes,
  onRouteBefore,
}
  • 只对component属性配置的组件处理为懒加载,不想用懒加载的就用element属性配置组件。
  • 自定义的meta数据也会以_meta字段名作为属性传给了每个路由组件,以备不时之需。
  • routes 是原始路由配置数据,有需要时可以读取使用。比如后台管理系统中可以根据此数据自动生成导航菜单,同时可通过meta字段配置路由权限方案。
  • 对于拦截函数onRouteBefore的返回值,如果存在异步的判断处理,可以return一个promise对象,promise里把要跳转的路由路径resolve出来即可。(不过这里的异步处理并不完美,实际上是先跳转原路由,再等异步完成后跳转另一路由。由于是基于react-router的element属性配置方案,暂未找到更好的解决方式。)

2、路由拦截处理

封装了页面路由容器组件,就是对路由做了一个包装,在路由渲染的时候就会执行里面的逻辑,然后调用路由拦截,做统一的路由前置钩子,既能做统一处理,也能控制拦截跳转。

guard.jsx

const pathRes = onRouteBefore({
    
     pathname, meta })
const pathResType = Object.prototype.toString.call(pathRes).match(/\s(\w+)\]/)[1]
if (pathResType === 'Promise') {
    
    
  pathRes.then(res => {
    
    
    if (res && res !== pathname) {
    
    
      navigate(res, {
    
     replace: true })
    }
  })
} else {
    
    
  if (pathRes && pathRes !== pathname) {
    
    
    element = <Navigate to={
    
    pathRes} replace={
    
    true} />
  }
}

return element

3、路由配置转换

将自定义的路由配置数组转换为react-router官方需要的路由数组,然后通过useRoutes方法来引用。
fn.js

/**
 * @description: 路由配置列表数据转换
 * @param {string} redirect 要重定向的路由路径
 * @param {function} component 函数形式import懒加载组件
 * @param {object} meta 自定义字段
 */
transformRoutes (routeList = this.routes) {
    
    
  const list = []
  routeList.forEach(route => {
    
    
    const obj = {
    
     ...route }
    if (obj.path === undefined) {
    
    
      return
    }
    if (obj.redirect) {
    
    
      obj.element = <Navigate to={
    
    obj.redirect} replace={
    
    true}/>
    }
    if (obj.component) {
    
    
      obj.element = this.lazyLoad(obj.component, obj.meta)
    }
    delete obj.redirect
    delete obj.component
    delete obj.meta
    if (obj.children) {
    
    
      obj.children = this.transformRoutes(obj.children)
    }
    list.push(obj)
  })
  return list
}

四、使用插件

目前上述完整代码已封装成插件,并上传到了npm。

使用方式:参考插件npm地址,传送门

五、其他

1、后台管理系统

(个人搭建的react后台管理系统项目:github传送门,目前正逐步完善,路由配置就是使用的当前方案,供参考学习,欢迎star。)

  • 嵌套路由配置方式

路由配置文件:/src/router/index.js

import PageLayout from '@/components/Layout/index'

const routes = [
  {
    
    
    path: '/',
    redirect: '/index',
  },
  {
    
    
    path: '/',
    element: <PageLayout />,
    children: [
      {
    
    
        path: 'index',
        component: () => import(/* webpackChunkName: "index" */ '@/views/index/index'),
        meta: {
    
    
          title: '首页',
          needLogin: true,
          roleId: 10000,
        },
      },
      ...... // 这里添加其他需要PageLayout页面布局的路由
    ]
  },
  ...... // 这里添加不需要PageLayout布局的路由,例如登录注册页
]

页面整体布局容器组件:/src/components/PageLayout/index.jsx

import SideBar from './sideBar' // 自定义的侧边栏
import HeadBar from './headBar' // 自定义的顶部头
import {
    
     Outlet } from 'react-router-dom' // 子路由出口,类似vue的router-view

function PageLayout () {
    
    
  return (
    <div className="c-Layout-index">
      <SideBar />
      <div className="appMainWrap">
        <HeadBar />
        <div className="appMain">
          <Outlet />
        </div>
        <Outlet />
      </div>
    </div>
  )
}

export default PageLayout

侧边栏菜单根据路由配置文件动态生成即可,需要权限判断的通过路由meta字段里自定义权限id(roleId)来匹配。

2、权限设计

使用本路由方案来进行前端react项目的权限设计也非常方便。
具体的设计方案可以参考这篇文章:传送门

猜你喜欢

转载自blog.csdn.net/u010059669/article/details/122359412