本文正在参加「金石计划 . 瓜分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
功能描述
- Page1是页面的首页
- Son1是Page1的子路由部分
- Page2页面
- Login页面
- 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
-
checkRoute:校验当前路由是否需要进行登录验证
-
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路由都会守卫到登录页面上。并且未存在的路由也能成功被拦截到兜底路由上。登录成功后,所有的页面都可以正常访问。
第二种:useRoutes管理
还是基于上面的路由页面进行设计。
2.1 createElement介绍
createElement
是 React 中用于创建虚拟 DOM 元素的函数。它接受三个参数
-
type:元素的类型,可以是字符串表示的 HTML 标签名,也可以是一个 React 组件。
-
props:元素的属性,一个包含了元素属性的对象,可以为 null 或者一个空对象
{}
。 -
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}</>
)
}
总结
以上分别使用三种路由管理进行路由守卫拦截,如果帮助到大家,希望动动小手点赞,关注,评论哦。