实现的功能:
下面分享具体方案。
(没耐心看实现原理的可直接跳到目录:四、使用插件
)
"react": "^17.0.2",
"react-router-dom": "^6.2.1",
"prop-types": "^15.8.1",
v6版本目前网上的文章寥寥无几,实现过程基本靠自己摸索。
useRoutes
。useRoutes
可以读取一个路由配置数组,生成相应的路由组件列表,类似以前的react-router-config
插件的功能,那么路由统一管理的实现用这个api就简单多了。实现的就是路由集中在一个文件里通过数组统一管理配置。
(这里仅介绍路由统一管理的配置方式示例,非完整代码)
项目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
}
引用路由并渲染的核心是利用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')
)
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属性。
(这里仅贴出部分最初版本的核心部分代码,至于使用方式和最新完整代码,可直接跳到下面的:“四、使用插件”)
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,
}
_meta
字段名作为属性传给了每个路由组件,以备不时之需。封装了页面路由容器组件,就是对路由做了一个包装,在路由渲染的时候就会执行里面的逻辑,然后调用路由拦截,做统一的路由前置钩子,既能做统一处理,也能控制拦截跳转。
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
将自定义的路由配置数组转换为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地址,传送门
(个人搭建的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)来匹配。
使用本路由方案来进行前端react项目的权限设计也非常方便。
具体的设计方案可以参考这篇文章:传送门。