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

  • 2022-02-18 更新:
    添加路由拦截异步处理的支持,通过拦截函数返回一个promise对象的形式使用。
  • 2022-02-11 更新:
    优化路由拦截函数的使用,避免循环引用的隐患。
  • 2022-01-13 更新:
    重构路由配置列表的配置方式,配置更加简便,且便于后台管理系统的项目根据路由配置自动生成导航菜单。

实现的功能:

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

下面分享具体方案。

一、react-router v6

"react": "^17.0.2",
"react-router-dom": "^6.2.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: ,
  },
  {
    path: '/login',
    element: ,
  },
  {
    path: '*',
    element: ,
  },
]

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(
  
    
      
    
  ,
  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、封装工具函数fn

新建文件src/components/RouterGuard/fn.js

import React from 'react'
import { Navigate } from 'react-router-dom'
import Guard from './guard'

let handleRouteBefore = null

// 设置路由导航守卫函数
function setRouteBefore (fn) {
  handleRouteBefore = fn
}

// 路由懒加载
function lazyLoad (importFn, meta) {
  meta = meta || {}
  const Element = React.lazy(importFn)
  const lazyElement = (
    
}> ) return ( ) } // 路由配置列表数据转换 function transformRoutes (routes) { const list = [] routes.forEach(route => { const obj = { ...route } if (obj.redirect) { obj.element = } if (obj.component) { obj.element = lazyLoad(obj.component, obj.meta) } delete obj.redirect delete obj.component delete obj.meta if (obj.children) { obj.children = transformRoutes(obj.children) } list.push(obj) }) return list } export { setRouteBefore, transformRoutes, }
  • lazyload函数里的fallback值就是路由切换时的loading组件,这个自行封装引用即可。
  • transformRoutes 方法对原始路由配置进行了递归遍历处理,转换成react-router需要的路由配置数据格式。
  • fallback属性值为路由切换时的loading效果,可自定义。一般移动端可能用的到,后台管理系统由于存在侧边栏导航栏等基础布局所以不建议使用。

2、封装路由容器组件guard

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

新建组件src/components/RouterGuard/guard.jsx

import { Navigate, useLocation, useNavigate } from 'react-router-dom'

let temp = null

function Guard ({ element, meta, handleRouteBefore }) {
  meta = meta || {}

  const location = useLocation()
  const { pathname } = location

  const navigate = useNavigate()

  if (handleRouteBefore) {
    if (temp === element) {
      return element
    }
    const pathRes = handleRouteBefore({ 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 = 
      }
    }
  }

  temp = element
  return element
}

export default Guard
  • StrictMode下组件会渲染两次,所以这里用temp变量处理了一下。

3、引用封装

(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)项目入口组件src/App.jsx里引用RouterGuard:

import { useRoutes } from 'react-router-dom'
import { routes, onRouteBefore } from '@/router'
import { transformRoutes, setRouteBefore } from '@/components/RouterGuard/fn'

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

export default App

四、其他

1、后台管理系统的使用

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

  • 嵌套路由配置方式

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

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

const routes = [
  {
    path: '/',
    redirect: '/index',
  },
  {
    path: '/',
    element: ,
    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 (
    
) } export default PageLayout

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

你可能感兴趣的:(前端,html,面试,react.js,javascript,前端)