一、概述
最近留意下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')
);