自己动手打造一款React路由守卫

3,196 阅读4分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」

引言

用过vue的小伙伴都知道,vue自带路由守卫钩子并且巨他妈的好用,而对于react开发者来说,在需要路由权限校验时常常存在许多痛点问题。今天我将为大家打造一款属于我们reacter的路由守卫方法,希望可以为大家提供帮助。

介绍

由于react路由统一管理不唯一,此处基于三种统一管理方式进行路由守卫。如果大家不清楚路由统一管理可以浏览这篇 juejin.cn/post/713052…

第一种:Router与Route路由管理

1.1 下载安装

npm install react-router-dom@6

1.2 index.tsx挂载

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <BrowserRouter>
        <App />
    </BrowserRouter>

);

1.3定义路由数组:route/index.ts

功能描述

  1. Page1是页面的首页
  2. Son1是Page1的子路由部分
  3. Page2页面
  4. Login页面
  5. NotFound页面,错误页面兜底

其中路由字段meta中的isCheck字段表示当前路由是否需要进行登录权限校验。

    import React, { ReactElement, createElement } from "react";
    import Page1 from "./../views/Page1";
    import Page2 from "./../views/Page2";
    import Login from "./../views/Login";
    import NotFound from "./../views/404";
    import Son1 from "./../views/Son1";
    interface metaType {
      name?: string;
      icon?: string;
      isCheck?:boolean
    }
    interface RouterType {
      path: string;
      children?: RouterType[];
      element?: ReactElement;
      meta?: metaType;
    }
    const routes: RouterType[] = [
      {
        path: "/",
        element: createElement(Page1),
        meta:{
          isCheck:true
        },
        children: [
          {
            path: "/son1",
            element: createElement(Son1),
            meta:{
              isCheck:true
            },
          },
        ],
      },
      {
        path: "/page2",
        element: createElement(Page2),
        meta: {
          isCheck:true
        },
      },
      {
        path: "/login",
        element: createElement(Login),
      },
      {
        path: "*",
        element: createElement(NotFound),
      },
    ];

    export default routes;

1.4 各页面代码

Login.tsx

export default function() {
  console.log('login load')
  const login = () => {
    localStorage.setItem('dzp','dsds');
  }
  const logout = () => {
    localStorage.removeItem('dzp');
  }
  return (
    <div>
      <h2>login pages</h2>
      <div onClick={()=>login()}>点击登录</div>
      <div onClick={()=>logout()}>退出登录</div>
    </div>
  )
}

Page1.tsx

import { Outlet } from 'react-router-dom';
export default function() {
  console.log('page1 load')
  return (
    <div>
      <h2>page1</h2>
      <Outlet/>
    </div>
  )
}

Page2.tsx

export default function() {
  console.log('page2 load')
  return (
    <div>
      <h2>page2</h2>
    </div>
  )
}

Son1.tsx

export default function() {
  console.log('son1 load')
  return <div>son1</div>
}

404.tsx

 export default function() {
  console.log('404 load')
  return <div>404</div>
}


1.5 App.tsx

  1. checkRoute:校验当前路由是否需要进行登录验证

  2. createRoute:递归生成Route组件,如果路由需要登录校验,并且未登录状态,路由的elemet绑定到Login登录组件上。

    import React from 'react';
    import {Routes,Route } from 'react-router-dom';
    import routes from "./router"
    import Login from './views/Login';
    function App() {
    
    
      const checkRoute = (item:any):boolean => {
        const check = item?.meta?.isCheck && !localStorage.getItem('dzp')
        return check;
      }
    
      const createRoute = (route:any) => {
        if(route.children) {
          return (
            <Route path={route.path} element={checkRoute(route)?<Login/>:route.element} key={route.path}>
              {route.children && route.children.map((itm:any)=>createRoute(itm))}
            </Route>
          )
        }
        return <Route path={route.path} element={checkRoute(route)?<Login/>:route.element} key={route.path}></Route>
      }
      return (
        <Routes>
          { routes.map((item:any)=>createRoute(item)) }
        </Routes>
      )  
    }
    export default App;
    

1.6 效果展示

可以发现,未登录状态下访问首页、page2、son1路由都会守卫到登录页面上。并且未存在的路由也能成功被拦截到兜底路由上。登录成功后,所有的页面都可以正常访问。

335.gif

第二种:useRoutes管理

还是基于上面的路由页面进行设计。

2.1 createElement介绍

createElement 是 React 中用于创建虚拟 DOM 元素的函数。它接受三个参数

  1. type:元素的类型,可以是字符串表示的 HTML 标签名,也可以是一个 React 组件。

  2. props:元素的属性,一个包含了元素属性的对象,可以为 null 或者一个空对象 {}

  3. children:元素的子节点,可以是单个子节点,也可以是子节点数组。

2.2 route/index.ts路由数组

import React, { ReactElement, createElement } from "react";
import Page1 from "./../views/Page1";
import Page2 from "./../views/Page2";
import Login from "./../views/Login";
import NotFound from "./../views/404";
import Son1 from "./../views/Son1";
import BeforeEnter from "./BeforeEnter";
interface metaType {
  name?: string;
  icon?: string;
  isCheck?:boolean
}
interface RouterType {
  path: string;
  children?: RouterType[];
  element?: ReactElement;
  meta?: metaType;
}
const routes: RouterType[] = [
  {
    path: "/",
    element: createElement(BeforeEnter,null,React.createElement(Page1)),
    meta:{
      isCheck:true
    },
    children: [
      {
        path: "/son1",
        element: createElement(BeforeEnter,null,React.createElement(Son1)),
        meta:{
          isCheck:true
        },
      },
    ],
  },
  {
    path: "/page2",
    element: createElement(BeforeEnter,null,React.createElement(Page2)),
    meta: {
      isCheck:true
    },
  },
  {
    path: "/login",
    element: createElement(Login),
  },
  {
    path: "*",
    element: createElement(NotFound),
  },
];

export default routes;

2.3 App.tsx

import React from 'react';
import { useRoutes } from 'react-router-dom';
import routes from "./router"
function App() {
  return (
    useRoutes(routes)
  )  
}
export default App;

2.4 BeforeEnter组件

BeforeEnter负责进行路由守卫校验。

import { useLocation,matchRoutes,Navigate } from "react-router-dom"
import routes from "./index";
import React from "react";
interface BeforeEachProps {
  children:React.ReactElement
}
export default function(props:BeforeEachProps) {
  const location = useLocation();
  const matchs = matchRoutes(routes,location);
  if(Array.isArray(matchs)) {
    const {route} = matchs[matchs.length-1]
    if(route?.meta?.isCheck && !localStorage.getItem('dzp')) {
      return <Navigate to="/login"/>
    }
  }
  return (
    <>{props.children}</>
  )
}

第三种:RouterProvider路由管理

3.1 index.tsx入口文件

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
    <App />
);

3.2 App.tsx

import React from 'react';
import { RouterProvider  } from 'react-router-dom';
import routes from "./router"
function App() {
  return (
    <RouterProvider router={routes}></RouterProvider>
  )  
}
export default App;

3.3 router/index.ts

import React, { ReactElement, createElement } from "react";
import Page1 from "./../views/Page1";
import Page2 from "./../views/Page2";
import Login from "./../views/Login";
import NotFound from "./../views/404";
import Son1 from "./../views/Son1";
import BeforeEnter from "./BeforeEnter";
import { createBrowserRouter } from "react-router-dom";
import type { RouteObject } from "react-router-dom";
interface metaType {
  name?: string;
  icon?: string;
  isCheck?:boolean
}
declare module "react-router" {
  interface IndexRouteObject {
    meta?:metaType
  }
  interface NonIndexRouteObject{
    meta?:metaType
  }
}
export const routes: RouteObject[] = [
  {
    path: "/",
    element: createElement(BeforeEnter,null,React.createElement(Page1)),
    meta:{
      isCheck:true
    },
    children: [
      {
        path: "/son1",
        element: createElement(BeforeEnter,null,React.createElement(Son1)),
        meta:{
          isCheck:true
        },
      },
    ],
  },
  {
    path: "/page2",
    element: createElement(BeforeEnter,null,React.createElement(Page2)),
    meta: {
      isCheck:true
    },
  },
  {
    path: "/login",
    element: createElement(Login),
  },
  {
    path: "*",
    element: createElement(NotFound),
  },
];

export default createBrowserRouter(routes);

3.4 router/BeforeEnter.tsx

 import { useLocation,matchRoutes,Navigate } from "react-router-dom"
import { routes } from "./index";
import React from "react";
interface BeforeEachProps {
  children?:React.ReactElement
}
export default function(props:BeforeEachProps) {
  const location = useLocation();
  const matchs = matchRoutes(routes,location);
  if(Array.isArray(matchs)) {
    const {route} = matchs[matchs.length-1]
    if(route?.meta?.isCheck && !localStorage.getItem('dzp')) {
      return <Navigate to="/login"/>
    }
  }
  return (
    <>{props.children}</>
  )
}

总结

以上分别使用三种路由管理进行路由守卫拦截,如果帮助到大家,希望动动小手点赞,关注,评论哦。