一、概述

        最近留意下react-router-dom有更新到6.6.1的版本,在这个版本增加了不少的功能。研究了下,可以利用其提供的API实现一个类似Vue的路由守卫,从而简便达到路由鉴权的业务场景。这里我是使用npm的包,是react-router-dom v6.5.0,但是基本新增的功能都有。

Github:码源

react-router-dom官网

二、createHashRouter

        这个函数可以帮助我们,采用编程的方式生成一个路由。

import { createHashRouter,useRouteError,redirect} from "react-router-dom";
import { Suspense, lazy } from 'react'
import React from "react";
import KeepAlive from 'react-activation'

type IRouterBeforeLoad = (res:any, redirectUrl: string) => Boolean;
let routerLoader: IRouterBeforeLoad;
let _redirectUrl: string = "/";
const routes = [
  {
    path: '/',
    auth: false,
    name:"login",
    component:lazy(() => import('@/page/login/login'))
  },
  {
    path: '/Portal',
    name:"Portal",
    component:lazy(() => import('@/page/portal/portal')),
    children: [
      { 
        path: '/Portal/Home',
        name:"Home",
        component:lazy(() => import('@/page/home/home'))
      },
      { 
        path: '/Portal/Lifecycle',
        name:"Lifecycle",
        component:lazy(() => import('@/page/lifecycle/lifecycle'))
      },
      { 
        path: '/Portal/NotFound',
        name:"NotFound",
        component:lazy(() => import('@/page/error/NotFound'))
      },
      { 
        path: '*',
        component:lazy(() => import('../page/error/NotFound'))
      },
    ]
  },
  { 
    path: '*',
    component:lazy(() => import('../page/error/NotFound'))
  },
]


function ErrorBoundary() {
  let error:any = useRouteError();
  return <div>
    <div>{ error.message}</div>
    <div>{ error.stack}</div>
  </div>;
  return <></>
}

// 路由处理方式
const generateRouter = (routers:any) => {
  return routers.map((item:any) => {
    if (item.children) {
      item.children = generateRouter(item.children)
    }
    item.element = <Suspense fallback={
      <div>加载中...</div>
    }>
      {/* 把懒加载的异步路由变成组件装载进去 */}
      <KeepAlive id={item.name} cacheKey={item.name}>
        <item.component />
      </KeepAlive>
    </Suspense>
    item.errorElement = <ErrorBoundary></ErrorBoundary>
    item.loader = async (res: any) => {
      if (routerLoader && !item.children) {
        if (routerLoader(res,_redirectUrl)) {
          return res;
        } else { 
          return redirect(_redirectUrl);
        }
      }
      return res;
    }
    return item
  })
}

const RouterLoader = (fun: IRouterBeforeLoad) => {
  routerLoader = fun;
}

const Router  = ()=>createHashRouter(generateRouter([...routes]))
export{ Router,RouterLoader}

三、errorElement、useRouteError

        路由中errorElement属性是新加的,其接受一个组件,在当前路由组件有错误的时候显示。但是这个功能和缓存组件<KeepAlive>组件有冲突。也就是目前使用缓存后,这个错误组件无法正常使用。可以留意下react-activation作者后续有没有修正这个bug。

        useRouteError这个hook是新增的,用于获取组件的报错信息。这里两个功能结合使用可以使错误信息自定义展示在页面,避免组件出错后,整个系统页面白屏。

 四、redirect

        重定向钩子,是目前官方提供的唯一一个非hook控制路由的钩子,其可以再纯js函数中使用,重新向路由。

五、自定义跳转hook,实现路由守卫RouterBeforeEach

        官方提供的路由跳转是使用useNavigate hook的,这里我们要实现路由守卫,那么我需要在路由跳转前做逻辑的判断,所以我自定义了一个useUtilsNavigate hook用于跳转前的判断。只要是通过这个hook跳转的路由都可以响应到路由守卫。

import { NavigateFunction, Location, To, NavigateOptions } from "react-router-dom";
import { RouterLoader } from "@/routes/route";
type IrouterBeforeLoad = (to:Ito,location?: Location) => Boolean;

interface Ito {
  to: To, options?: NavigateOptions
 }

let routerBeforeLoad: IrouterBeforeLoad;
let flag: Boolean = true;

const RouterBeforeEach = (fun: IrouterBeforeLoad) => {
  ///页面刷新时,配合loader实现调用,并做拦截重定向,由flag判断是否是初次刷新页面,以免在useUtilsNavigate调用是触发多次路由校验
  RouterLoader((res: any,redirectUrl:string) => { 
    let result: Boolean=true;
    if (flag) { 
      let url = new URL(res.request.url)
      result = fun({ to: url.pathname })
      if (redirectUrl==url.pathname) { 
        result = true;
      }
    }
    return result;
  })
  routerBeforeLoad = fun;
}

///所有的js路由跳转通过此函数,由此做路由拦截
const useUtilsNavigate=(navigate:NavigateFunction,location:Location,to: To, options?: NavigateOptions)=>{
  if (routerBeforeLoad && routerBeforeLoad({ to, options }, location)) {
    //flag设置false标志已经不是第一次加载页面
    flag = false;
    navigate(to, options)
  } else {
    return;
  }
  //flag设置false标志已经不是第一次加载页面
  flag = false;
  navigate(to,options)
}

export { useUtilsNavigate, RouterBeforeEach };
export type { IrouterBeforeLoad,Ito };

RouterLoader这个函数钩子是在路由定义的文件里面导出的,可以看到在route.tsx,其在loader属性里面被调用。loader这个也是新版本提供的一个新功能,其会在组件页面加载时先回调这个钩子,我在这里根据flag判断是否页面初始加载。因为页面通过URL直接打开的话,是没有经过useUtilsNavigate,也就是无法通过它去做路由监听,所以需要使用loader这个钩子,在初次加载时,触发路由守卫。

六、在程序主入口注册路由守卫钩子

import ReactDOM from 'react-dom';
import {RouterProvider } from "react-router-dom";
import './index.css'
import { Router } from './routes/route';
import { AliveScope } from 'react-activation'
import React from 'react';
import { RouterBeforeEach } from "@/utils/useUtilsNavigate";
RouterBeforeEach(( to,from) => { 
  console.log("路由守卫to", to)
  console.log("路由守卫from", from)
  return true;
})

ReactDOM.render(
    <AliveScope>
      <RouterProvider router={Router()}/>
    </AliveScope>,
  document.getElementById('root')
);

Logo

前往低代码交流专区

更多推荐