I. Overview
Recently, I noticed that react-router-dom has been updated to version 6.6.1, and a lot of functions have been added in this version. After researching, you can use the API provided by it to implement a Vue-like routing guard, so as to easily achieve the business scenario of routing authentication. Here I use the npm package, which is react-router-dom v6.5.0, but there are basically new functions.
Github:code source
react-router-dom official website
Two, createHashRouter
This function can help us generate a route programmatically.
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 <></> } // Route handling const generateRouter = (routers:any) => { return routers.map((item:any) => { if (item.children) { item.children = generateRouter(item.children) } item.element = <Suspense fallback={ <div>Loading...</div> }> {/* Turn lazy-loaded asynchronous routes into components and load them in */} <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}
Three, errorElement, useRouteError
The errorElement attribute in routing is newly added, which accepts a component and displays it when the current routing component has an error. But this function conflicts with the cache component <KeepAlive> component. That is, after using the cache at present, this wrong component cannot be used normally. You can pay attention to whether the author of react-activation has fixed this bug in the future.
The hook useRouteError is newly added and is used to obtain the error message of the component. The combination of the two functions here can make the error message displayed on the page in a custom way, avoiding the blank screen of the entire system page after a component error occurs.
Four, redirect
The redirection hook is currently the only officially provided non-hook control routing hook, which can be used in pure js functions to redirect the routing.
5. Customize the jump hook to realize the routing guard RouterBeforeEach
The official routing jump uses the useNavigate hook. Here we want to implement the routing guard, so I need to make a logical judgment before the routing jump, so I customized a useUtilsNavigate hook for the judgment before the jump. As long as the route jumps through this hook, it can respond to the route guard.
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) => { /// When the page is refreshed, cooperate with the loader to implement the call, and intercept and redirect, and use the flag to judge whether it is the first refresh of the page, so as not to trigger multiple routing checks when calling 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; } ///All js routing jumps through this function, thus doing routing interception const useUtilsNavigate=(navigate:NavigateFunction,location:Location,to: To, options?: NavigateOptions)=>{ if (routerBeforeLoad && routerBeforeLoad({ to, options }, location)) { //flag setting false flag is not the first time to load the page flag = false; navigate(to, options) } else { return; } //flag setting false flag is not the first time to load the page flag = false; navigate(to,options) } export { useUtilsNavigate, RouterBeforeEach }; export type { IrouterBeforeLoad,Ito };
The RouterLoader function hook is exported in the route definition file, you can see that it is called in the loader attribute in route.tsx. loader is also a new function provided by the new version. It will call back this hook when the component page is loaded. Here I judge whether the page is initially loaded according to the flag. Because if the page is opened directly through the URL, it does not go through useUtilsNavigate, that is, it cannot be used for routing monitoring, so you need to use the loader hook to trigger the routing guard when it is loaded for the first time.
6. Register the routing guard hook at the main entrance of the program
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("route guard to", to) console.log("route guard from", from) return true; }) ReactDOM.render( <AliveScope> <RouterProvider router={Router()}/> </AliveScope>, document.getElementById('root') );