git地址:https://github.com/ReactTraining/react-router/tree/master/packages/react-router-config
react-router-config主要用来帮助我们进行集中式路由的配置,在不使用react-router-config之前,我们的路由使用react-router-dom库来进行配置,类似如下代码:
import React from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
} from "react-router-dom";
import Home from './pages/Home';
import Login from './pages/Login';
import Backend from './pages/Backend';
import Admin from './pages/Admin';
function App() {
return (
<Router>
<Switch>
<Route path="/login" component={Login}/>
<Route path="/backend" component={Backend}/>
<Route path="/admin" component={Admin}/>
<Route path="/" component={Home}/>
</Switch>
</Router>
);
}
export default App;
react-router-config可以使得路由配置更加简便
routes.ts路由配置
import { RouteConfig } from 'react-router-config';
import Home from '../components/Home';
import Inquiry from '../components/Inquiry';
import Offer from '../components/Offer';
const routes: RouteConfig = [
{
path: '/',
component: Home,
routes: [
{
path: '/inquiry',
component: Inquiry
},
{
path: '/offer',
component: Offer
}
]
}
]
export default routes
index.js入口文件
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'mobx-react';
import './index.css';
import { renderRoutes } from 'react-router-config';
import { HashRouter } from 'react-router-dom';
import routes from './config/routes';
import stores from './stores/index';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<HashRouter>
<Provider {...stores}>
{renderRoutes(routes)}
</Provider>
</HashRouter>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
Home组件:
import React from 'react';
import { renderRoutes } from 'react-router-config';
const Home = (props: any) => {
const route = props.route;
return <div>
<div>this is Home page.</div>
{route && renderRoutes(route.routes)}
</div>
}
export default Home;
Inquiry组件:
import React from 'react';
const Inquiry = () => {
return <div>Inquiry</div>
}
export default Inquiry;
Offer组件
import React from 'react';
const Offer = () => {
return <div>Offer</div>
}
export default Offer;
下面代码实现了react-router-config的功能
1.实现集中式路由配置
router.js
import React, { Suspense, lazy } from 'react'
import { Route, Switch, Redirect } from 'react-router-dom'
import { matchPath, Router } from "react-router"
// component: React.lazy(() => import("pages/table-poc"))
// component: lazy(() => import("@/pages/user/All"))
// component: require('../a/b/index.js').default,
//const Game = React.lazy(() => new Promise( (resolve) => {
// setTimeout( () => resolve(import('./Game')) , 1000)
//}))
// 使用React自带的 Suspense,lazy实现懒加载
const routes = [
{ path: "/", exact: true, render: () => <Redirect to={"/login/password"} /> },
{ path: "/login", exact: true, render: () => <Redirect to={"/login/password"} /> },
{ path: "/principal", exact: true, render: () => <Redirect to={"/principal/teacher"} /> },
{ path: "/teacher", exact: true, render: () => <Redirect to={"/teacher/textbook"} /> },
{
path: '/login/:type',
name: 'Login',
component: lazy(() => import("pages/login/login.jsx")),
meta: { title: '登录' }
},
{
path: '/password',
name: 'Login',
component: lazy(() => import("pages/login/login.jsx")),
meta: { title: '修改密码' }
},
{
path: '/teacher',
name: 'Teacher',
component: lazy(() => import('pages/layout/layout.jsx')),
meta: { title: '教师端' },
routes: [
{
path: '/teacher/textbook',
name: 'TeacherTextbook',
component: lazy(() => import('pages/teacher/textbook/textbook.jsx')),
meta: { title: '选课' },
routes: [
{
path: '/teacher/textbook/index',
name: 'TeacherTextbookIndex',
component: lazy(() => import('pages/test.jsx')),
meta: { title: '测试啊' },
}
]
},
{
path: '/teacher/in-class/:id/:name',
name: 'TeacherInclass',
component: lazy(() => import('pages/teacher/in-class/in-class.jsx')),
meta: { title: '上课' }
}
]
}
]
// 实现react-router-config里的renderRoutes方法
function renderRoutes (routes, extraProps = {}, switchProps = {}) {
return routes ? (
<Suspense fallback={<div>页面加载中...</div>}>
<Switch {...switchProps}>
{routes.map((route, i) => (
<Route
key={route.key || i}
path={route.path}
exact={route.exact}
strict={route.strict}
render={props =>
route.render ? (
route.render({ ...props, ...extraProps, route: route })
) : (
<route.component {...props} {...extraProps} route={route} />
)
}
/>
))}
</Switch>
</Suspense>
) : null;
}
// 实现react-router-config里的matchRoutes方法
function matchRoutes (routes, pathname, /*not public API*/ branch = []) {
routes.some(route => {
const match = route.path
? matchPath(pathname, route)
: branch.length
? branch[branch.length - 1].match // use parent match
: Router.computeRootMatch(pathname); // use default "root" match
if (match) {
branch.push({ route, match });
if (route.routes) {
matchRoutes(route.routes, pathname, branch);
}
}
return match;
});
return branch;
}
export { routes, renderRoutes, matchRoutes }
App.js
import './App.scss';
import { BrowserRouter } from 'react-router-dom'
import { routes, renderRoutes } from './router'
function App () {
return (
<BrowserRouter>
{/* 这个方法,每次有子路由时都需要使用,会传当前路由的子路由,可以说是按需加载,
实时编译的,而不是一次性吧所有路由都渲染出来 */}
{renderRoutes(routes)}
</BrowserRouter>
)
}
export default App
其他页面调用时:{renderRoutes(this.props.route.routes)}
2.实现面包屑
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { renderRoutes, matchRoutes, routes } from 'router'
export default class Layout extends Component {
render () {
const branch = matchRoutes(routes, this.props.location.pathname)
return (
<div>
<header>
{/* 面包屑 */}
{
branch.map((item, i) => {
return item.match.url === pathname ?
<span key={i}>{item.route.meta.title}</span> :
<Link to={item.match.path} key={i} className='back'>{item.route.meta.title}</Link>
})
}
</header>
{/* 路由 */}
{renderRoutes(nowRoutes)}
</div>
)
}
}
3.路由鉴权
以下是个人重写,添加了多重权限验证以及多重路由匹配(即添加渲染非<Switch>包裹的<Route>)。
export interface IRouteConfig extends RouteConfig {
auth?: number;
routes?: IRouteConfig[];
multipleRoutes?: IRouteConfig[];
}
/**
* 将路由配置渲染成节点
* @param routes switch路由列表
* @param authed 当前账号权限
* @param multipleRoutes 非switch路由列表,将会在Switch节点前渲染Route
* @param extraProps 添加额外的Route props
* @param switchProps Switch props
*/
function renderRoutes(
routes: IRouteConfig[] | undefined,
authed: number,
multipleRoutes?: IRouteConfig[],
extraProps?: any,
switchProps?: SwitchProps
) {
const isMobile = checkMobile();
let list = [];
const mapFunc = (R: IRouteConfig[]) =>
R.map((route, i) => (
<Route
key={route.key || i}
path={route.path}
exact={route.exact}
strict={route.strict}
render={props => {
// 将authed赋值到route,试子组件可以通过route.authed获取当前用户权限
if (authed !== undefined) route.authed = authed;
// 不存在authed或者authed大于当前路由权限,即可渲染组件,否则跳转登录界面
if (!route.auth || route.auth <= authed) {
return route.render
? route.render({ ...props, ...extraProps, route: route })
: route.component && (
<route.component {...props} {...extraProps} route={route} />
);
} else {
message.warn("请先登录!");
return (
<Redirect to={route.auth <= 1 "/user/loginRegister/login"} />
);
}
}}
/>
));
if (routes) {
list.push(
<Switch {...switchProps} key="biubiubiu~~">
{mapFunc(routes)}
</Switch>
);
// 将非Switch包裹的Route挂载到Switch节点之前
multipleRoutes && list.unshift(...mapFunc(multipleRoutes));
// 返回一个数组,[<Route/>,...,<Route/>,<Switch>...</Switch>](实际元素并非如此结构,此处仅为方便说明写的伪代码),React会将数组渲染成节点
return list;
}
}
修改后的routes,当匹配到"/user/all/article/(id值)"的路径,页面会同时渲染Article以及All两个组件,未登录则渲染Article和Login组件,mutipleRoutes主要是为了能通过多重匹配路由模拟VUE的keep-alive效果。代码如下👇:
export const mobileRouterList: IRouteConfig[] = [
{
path: "/",
exact: true,
render: () => <Redirect to="/user/all" />
},
{
path: "/user",
component: MobileMain,
multipleRoutes: [
{ path: "/user/:page/article/:id", component: Article }
],
routes: [
{
path: "/user/all",
component: lazy(() => import("@/pages/user/All"))
},
{
path: "/user/me",
auth: 1, // 用户权限必须 >=1 才可以访问
component: lazy(() => import("@/pages/user/Me"))
},
{
path: "/admin/controlPanel",
auth: 2, // 用户权限必须 >=2(管理员) 才可以访问
component: lazy(() => import("@/pages/admin/ControlPanel"))
}
]
}
]
修改后的rednerRoutes使用,后面参数选填:
// 顶层使用路由渲染,手动添加第二参数
renderRoutes(routes,user.authed)
// 子层组件路由渲染
renderRoutes(props.route.routes,<props.route.authed>,<props.route.multipleRoutes>,...)
以下是自己封装的路由鉴权(判断是否登录以及页面的访问权限)
import React, { Suspense, lazy } from 'react'
import { Redirect } from 'react-router-dom'
import { Route, Switch } from 'react-router'
// exact属性为true时路径中的hash值必须和path完全一致才渲染对应的组件,如果为false则'/'也可以匹配'/login';
// (如果strict属性为false,则末尾是否包含反斜杠结尾不影响匹配结果)
// strict属性主要就是匹配反斜杠,规定是否匹配末尾包含反斜杠的路径,如果strict为true,则如果path中不包含反斜杠结尾,
// 则他也不能匹配包含反斜杠结尾的路径,这个需要和exact结合使用
const permissions = 'user'//登录接口获取的当前用户的角色
const requiresAuth = true //是否已经登录
//所有的路由
export const routes = [
{
path: '/',
exact: true,
requiresAuth: false,
permissions: ['user', 'admin'],
render: () => <Redirect to='/login' />
},
{
path: '/login',
name: 'Login',
exact: true,
strict: true,
requiresAuth: false,//是否需要登录
permissions: ['user', 'admin'], // 当前登录权限必须 user或admin 才可以访问
component: lazy(() => import('../pages/login.jsx')),//路由懒加载
meta: { title: '登录呢', icon: 'login' }
},
{
path: '/JsPlan1',
name: 'JsPlan1',
exact: true,
requiresAuth: true,
permissions: ['user', 'admin'],
component: lazy(() => import('../pages/js/plan1.jsx')),
meta: { title: 'js的plan1', icon: 'user' }
},
{
path: '/JsPlan2',
name: 'JsPlan2',
exact: true,
requiresAuth: true,
permissions: ['admin'],
component: lazy(() => import('../pages/js/plan2.jsx')),
meta: { title: 'js的plan2' }
},
{
path: '/TsPlan1',
name: 'TsPlan1',
exact: true,
requiresAuth: true,
permissions: ['admin'],
component: lazy(() => import('../pages/ts/plan1.tsx')),
meta: { title: 'ts的plan1' }
},
{
path: '/TsPlan2',
name: 'TsPlan2',
exact: true,
requiresAuth: true,
permissions: ['user', 'admin'],
component: lazy(() => import('../pages/ts/plan2.tsx')),
meta: { title: 'ts的plan2' }
},
]
export const renderRoutes = (routes, extraProps = {}, switchProps = {}) => {
return routes ? (
<Suspense fallback={<div>页面加载中...</div>}>
<Switch {...switchProps}>
{routes.map((item, i) => (
<Route
key={item.name || i}
path={item.path}
exact={item.exact}
strict={item.strict}
render={props => routeRender(item, props, extraProps)}
/>
))}
</Switch>
</Suspense>
) : null
}
const routeRender = (route, props, extraProps) => {
// 登录判断(需要登录 && 未登录-->跳登录页面,,,,,,,不需要登录 || 已经登录-->正常跳转)
const login = route.requiresAuth && !requiresAuth //跳登录
// 该角色是否有权限访问该页面(当前角色是否在 路由要求角色数组中)
const auth = route.permissions.includes(permissions) //有权限
console.log(222, '是需要跳转登录页面' + login, '是否有权限' + auth, props)
// 判断渲染route
if (login) {
return <Redirect to={{ path: '/login', message: '请登录后再操作!' }} />
} else {
if (auth) {
return route.render ? (
route.render({ ...props, ...extraProps, route: route })
) : (
<route.component {...props} {...extraProps} route={route} />
)
} else {
alert('您暂无权限')
return <Redirect to={{ path: '/login', message: '请登录后再操作!' }} />
}
}
}