React中实现路由守卫一直不太容易,在使用umi
的时候,发现有wrapper
中间件的语法, 本质上是个HOC
, 但优点是不用侵入到组件内部去进行修改,而是可以在外部的路由配置表router/config.js
中直接完成
[
{
path: '/connect',
component: '@/pages/connect',
wrappers: [
'@/wrappers/withAuth'
],
},
]
下面尝试基于react-router-dom
自己实现一个, 使用方式如下
const config: IRouteObject = {
path: '/',
element: <App />,
wrapper: [WithAuth],
children: [
{
path: '/courseware',
element: <Courseware />,
wrapper: [WithEvent],
children: [],
},
],
};
接下来会具体讲讲实现的过程
WithAuth是一个 HOC
const WithAuth: React.FC = (WrappedComponent) => {
// WithAuth 高阶组件的实现, 包括与身份验证相关的逻辑, 以及决定是否渲染包装的组件等, 为简单起见,未提供实际的实现。
// 返回包装的组件
return <WrappedComponent />;
};
在路由配置(config 对象)中,wrapper 属性是一个包含 WithAuth(HelloWorld)都是指定的。它表示这些组件应该使用 WithAuth 高阶组件进行包装。
import React from 'react';
import { createBrowserRouter } from 'react-router-dom';
import { Route } from './route';
import { IRouteObject } from './types';
import { wrapperHandler } from './wrapperHandler';
import { getOrCreateRouter, registerRoute } from './router';
import WithAuth from '@/HOC/WithAuth';
import WithEvent from '@/HOC/WithEvent';
const App = React.lazy(() => import(/* webpackChunkName: "App" */ '@/pages/App'));
const HelloWorld = React.lazy(() => import(/* webpackChunkName: "HelloWorld" */ '@/pages/HelloWorld'),);
const config: IRouteObject = {
path: '/',
element: <App />,
wrapper: [WithAuth],
children: [
{
path: '/HelloWorld',
element: <HelloWorld />,
wrapper: [WithEvent],
children: [],
},
],
};
const route = new Route(config, wrapperHandler);
registerRoute(route);
const routerObject: any = getOrCreateRouter().router;
const router = createBrowserRouter(routerObject);
export default router;
wrapperHandler 函数负责对给定路由应用指定的包装器。它递归处理路由及其子项,对每个组件应用包装器。
export function wrapperHandler(route: IRouteObject): IRouteObject {
const { wrapper = [], element, children, ...restParams } = route;
// 处理指定的包装器
const WrapperElement = wrapper.reduce(
(reactNode, hoc) => {
const node = () => {
return <Suspense fallback={<></>}>{reactNode({})}</Suspense>;
};
return hoc(node, { ...restParams, children });
},
(() => {
return element;
}) as React.FC,
);
// 递归处理子项
const wrapperChildren = children?.map(wrapperHandler);
// 返回更新后的路由,其中包含包装后的元素和子项
return { ...route, element: <WrapperElement />, children: wrapperChildren } as any;
}
Route.ts
import { IRouteHandler, IRouteObject } from './types';
export class Route {
private readonly _route: IRouteObject;
private readonly _handlerList: IRouteHandler[] = [];
constructor(route: IRouteObject, handler?: IRouteHandler[] | IRouteHandler) {
/** routeObject -> route的配置项 */
this._route = route;
/** route处理器 */
handler && (this._handlerList = this._handlerList.concat(handler));
}
get route() {
return this._route;
}
get handlerList() {
return this._handlerList;
}
}
Router.ts
import { logger } from '@/helpers/logger';
import { Route } from './route';
import { IRouteHandler, IRouteObject } from './types';
type IRouteOptions = { route: IRouteObject; handler?: IRouteHandler };
let router: Router;
class Router {
router: IRouteObject[] = [];
constructor(...rest: Route[]) {
this.push(...rest);
}
push(...rest: Route[]) {
// 没有参数不执行后续操作
if (!rest.length) {
return;
}
// 得到handler处理后的routeList,如果不存在handler,则直接返回routeObject
const handleRouteList = rest.map(route => {
const { route: routeObject } = route;
// 得到handler处理route后route,没有handler返回undefined
const handleRouteObject = this._getHandledRoute(route);
return handleRouteObject || routeObject;
});
this.router = this.router.concat(...handleRouteList);
}
private _getHandledRoute(route: Route) {
const { handlerList, route: routeObject } = route;
let handleRouteObject: IRouteObject | undefined;
if (handlerList.length === 0) {
return;
}
for (const handler of handlerList) {
// 不支持promise
if (handler instanceof Promise) {
logger.warn('router类的_getHandledRoute警告: route处理函数handler不能是异步函数');
continue;
}
handleRouteObject = handler(routeObject);
}
return handleRouteObject;
}
}
/** 注册route到router */
export function registerRoute(...rest: Route[] | IRouteOptions[]) {
if (rest.length === 0) {
return;
}
if (!router) {
router = new Router();
}
const routeList = rest.map(options => {
if (options instanceof Route) {
return options;
} else {
return new Route(options.route, options.handler);
}
});
router.push(...routeList);
}
// 获取router
export function getOrCreateRouter() {
if (!router) {
router = new Router();
}
return router;
}