Skip to content

Latest commit

 

History

History
executable file
·
222 lines (180 loc) · 5.49 KB

如何实现路由中间件.md

File metadata and controls

executable file
·
222 lines (180 loc) · 5.49 KB

前言

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;
}