三分钟实现一个react-router-dom5.0的路由拦截(导航守卫)

2023-11-19

不同于vue,通过在路由里设置meta元字符实现路由拦截。在使用 Vue ,框架提供了路由守卫功能,用来在进入某个路有前进行一些校验工作,如果校验失败,就跳转到 404 或者登陆页面,比如 Vue 中的 beforeEnter 函数:

...
router.beforeEach(async(to, from, next) => {
    const toPath = to.path;
    const fromPath = from.path;
})
...

react实现路由拦截的基本思路还是利用Route 的render函数。通过判断拦截条件来实现不同的组件的跳转,从而实现拦截。在之前的版本中,React Router 也提供了类似的 onEnter 钩子,但在 React Router 4.0 版本中,取消了这个方法。React Router 4.0 以后采用了声明式的组件,路由即组件,要实现路由守卫功能,就得我们自己去写了。
如果不使用路由守卫,Router 组件是这样子的:

import * as React from 'react';
import { HashRouter,Switch,Route,Redirect } from 'react-router-dom';

import Index from "./page/index";
import Home from "./page/home";
import ErrorPage from "./page/error";
import Login from "./page/login";
 
export const Router = () => (
    <HashRouter>
        <Switch>
            <Route path="/" exact component={Index}/>
            <Route path="/login" exact component={Login}/>
            <Route path="/home" exact component={Home}/>
            <Route path="/404" exact component={ErrorPage}/>
            <Redirect to="/404" />
        </Switch>
    </HashRouter>
);

上面的 Router 组件,包含了三个页面:

  • 登陆
  • 主页
  • 404 页面

以及四个路由:

  • 根路由
  • 登陆路由
  • 主页路由
  • 404 路由

其中,根路由和 /home 路由,都定向到了主页路由。
以上是一个基本的路由定义,可以在登陆/主页和 404 页面之间来回跳转,但也有一些问题:

  • 非登陆状态下,可以直接跳转到主页
  • 登陆状态下,也可以输入 /login 路由跳转到登录页

现在,我们想完成这样的功能:

  • 非登陆状态下,无法直接跳转到主页,如果在非登陆状态下进行主页跳转,需要重定向至登陆路由
  • 登陆状态下,无法跳转至登录页,如果在登陆状态下进行登陆页跳转,需要重定向至主页路由

要完成这个功能,有两种方案:

  • 在每个组件中,根据 props 上的 history 对象来进行跳转
  • 进行全局的路由守卫处理

首先在跟目录src下创建一个routerMap.js文件,代码如下: 

 

import Index from "./page/index";
import Home from "./page/home";
import ErrorPage from "./page/error";
import Login from "./page/login";
export default [
  { path: "/", name: "App", component: Index, auth: true },
  { path: "/home", name: "Home", component: Home, auth: true },
  { path: "/login", name: "Login", component: Login },
  { path: "/404", name: "404", component: ErrorPage },
];

将 auth 设置为 true,表示该路由需要权限校验。
然后,定义 Router 组件,在App.js中,该组件是经过高阶组件包装后的结果:

//App.js
import React, { Component } from "react";
import { BrowserRouter as Router, Switch } from "react-router-dom";
import FrontendAuth from "./FrontendAuth";
import routerMap from "./routerMap";
class App extends Component {
  // eslint-disable-next-line no-useless-constructor
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <Router>
        <div>
          <Switch>
            <FrontendAuth routerConfig={routerMap} />
          </Switch>
        </div>
      </Router>
    );
  }
}

export default App;

所有的路由跳转,都交给 FrontendAuth 高阶组件代理完成。下面是 FrontendAuth 组件的实现:

import React, { Component } from "react";
import { Route, Redirect } from "react-router-dom";
class FrontendAuth extends Component {
  // eslint-disable-next-line no-useless-constructor
  constructor(props) {
    super(props);
  }
  render() {
    const { routerConfig, location } = this.props;
    const { pathname } = location;
    const isLogin = sessionStorage.getItem("username");
    console.log(pathname, isLogin);
    console.log(location);
    // 如果该路由不用进行权限校验,登录状态下登陆页除外
    // 因为登陆后,无法跳转到登陆页
    // 这部分代码,是为了在非登陆状态下,访问不需要权限校验的路由
    const targetRouterConfig = routerConfig.find(
      (item) => item.path === pathname
    );
    console.log(targetRouterConfig);
    if (targetRouterConfig && !targetRouterConfig.auth && !isLogin) {
      const { component } = targetRouterConfig;
      return <Route exact path={pathname} component={component} />;
    }
    if (isLogin) {
      // 如果是登陆状态,想要跳转到登陆,重定向到主页
      if (pathname === "/login") {
        return <Redirect to="/" />;
      } else {
        // 如果路由合法,就跳转到相应的路由
        if (targetRouterConfig) {
          return (
            <Route path={pathname} component={targetRouterConfig.component} />
          );
        } else {
          // 如果路由不合法,重定向到 404 页面
          return <Redirect to="/404" />;
        }
      }
    } else {
      // 非登陆状态下,当路由合法时且需要权限校验时,跳转到登陆页面,要求登陆
      if (targetRouterConfig && targetRouterConfig.auth) {
        return <Redirect to="/login" />;
      } else {
        // 非登陆状态下,路由不合法时,重定向至 404
        return <Redirect to="/404" />;
      }
    }
  }
}
export default FrontendAuth;

创建登录的界面src/page/login.js,代码如下:

import React, { Component } from "react";
import { Redirect } from "react-router-dom"; //重定向使用
class Login extends Component {
  constructor(props) {
    super(props);
    this.state = {
      username: "",
      password: "",
      rediectToReferrer: false, // 是否重定向之前的界面
    };
    this.handleChange = this.handleChange.bind(this);
    this.handleSumit = this.handleSumit.bind(this);
  }
  // 处理用户名、密码的变化
  handleChange(e) {
    if (e.target.name === "username") {
      this.setState({
        username: e.target.value,
      });
    } else if (e.target.name === "password") {
      this.setState({
        password: e.target.value,
      });
    }
  }
  // 提交登录表单
  async handleSumit(e) {
    e.preventDefault();
    const username = this.state.username;
    const password = this.state.password;
    if (username.length === 0 || password.length === 0) {
      alert("用户名或密码不能为空!");
      return;
    }
    // 保存信息到sessionStorage
    sessionStorage.setItem("username", username);
    // 登录成功后,设置redirectToReferrer为true;
    this.setState({
      rediectToReferrer: true,
    });
    let RedirectUrl = this.props.location.state
      ? this.props.location.state.from.pathname
      : "/";
    // 登陆成功之后的跳转
    this.props.history.push(RedirectUrl);
  }
  render() {
    return (
      <form className="login" onSubmit={this.handleSumit}>
        <div>
          <label htmlFor="">
            用户名:
            <input
              type="text"
              name="username"
              value={this.state.username}
              onChange={this.handleChange}
            />
          </label>
        </div>
        <div>
          <label htmlFor="">
            密码:
            <input
              type="password"
              name="password"
              value={this.state.password}
              onChange={this.handleChange}
            />
          </label>
        </div>
        <input type="submit" value="登录" />
      </form>
    );
  }
}
export default Login;

全部代码目录如下:

页面上的路由跳转,都由 FrontendAuth 高阶组件代理了,在 Switch 组件内部,不再是 Route 组件,而只有一个 FrontendAuth 组件。
FrontendAuth 组件接收一个名为 config 的 Props,这是一份路由表。同时,由于 FrontendAuth 组件放在了 Switch 组件内部,React Router 还自动为 FrontendAuth 注入了 location 属性,当地址栏的路由发生变化时,就会触发 location 属性对象上的 pathname 属性发生变化,从而触发 FrontendAuth 的更新(调用 render 函数)。
FrontendAuth 的 render 函数中,根据 pathname 查找到路由表中的相关配置,如果该配置中指定了无需校验,就直接返回相应的 Route 组件。
如果查找到的配置需要进行校验,再根据是否登陆进行处理,具体可以查看代码中的注释。

总结一下,实现路由守卫需要考虑到以下的问题:

  1. 未登录情况下,访问不需要权限校验的合法页面:允许访问
  2. 登陆情况下,访问登陆页面:禁止访问,跳转至主页
  3. 登陆情况下,访问除登陆页以外的合法页面:允许访问
  4. 登陆情况下,访问所有的非法页面:禁止访问,跳转至 404
  5. 未登录情况下,访问需要权限校验的页面:禁止访问,跳转至登陆页
  6. 未登录情况下,访问所有的非法页面:禁止访问,跳转至 404

源码链接

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

三分钟实现一个react-router-dom5.0的路由拦截(导航守卫) 的相关文章

  • 如何在reactjs中访问悬停状态?

    我有一个侧面导航 里面有很多篮球队 因此 当其中一个团队悬停在上方时 我想为每个团队显示不同的内容 另外 我正在使用 Reactjs 所以如果我可以有一个可以传递给另一个组件的变量 那就太棒了 React 组件在其顶级界面中公开所有标准 J
  • 以编程方式填写reactjs表单

    我正在编写一个用户脚本 但无法填写由reactjs制作的表单 我的代码 document querySelector id username value email protected cdn cgi l email protection
  • React-Redux:state.setIn() 和 state.set() 有什么区别?

    我见过使用setIn and set 在一些react redux代码中 state setIn state set 我在这里找到了一些文档https facebook github io immutable js https facebo
  • 使用 ApolloClient 从 useContext 挂钩导入一个简单变量

    我的反应相对较新 似乎无法导入client来自上下文的变量 我有一个名为 federation tsx 的文件 其中包含一些代码 我相信这应该是相关部分 const link createHttpLink uri process env U
  • 陷入了 React useEffect 的一系列变化

    我正在反应中创建一个表单生成器 在一个简单的文本字段上 我需要 Change currentValue When currentValue已更改 验证它 验证完成后 发出一个事件 有效或无效 这是我的代码 const TextInput p
  • React Typescript - 在路由中传递时如何将类型添加到 location.state

    当我将react router props发送到路由中的组件时 我收到错误 因为我有特定的状态 我传递了该组件 但错误显示在我的路由中 这是路线代码
  • 如何在appendchild方法javascript或reactjs中使用文件夹路径中的svg?

    我是 ReactJS 的新手 我坚持将 svg 元素附加到 div 元素 我想实现什么 我有一个动态创建的 div 元素 如下所示 constructor props super props this element document cr
  • React Native 中循环 Json 并显示

    How do I go about looping the result i retrieved from Json render function console log this state list contents
  • 为什么使用 getDerivedStateFromProps 而不是 componentDidUpdate?

    正如读到的这个 React Github 问题 https github com facebook react issues 12310我看到越来越多的 的代价render 相对较小 在 React 16 3 中 我想知道为什么要使用新的
  • 嵌套对象的 setState

    我有一个嵌套对象作为状态 并且在组件中有一个表单 我正在考虑每次用户在表单中输入某些内容时更新状态 并且为了避免为每个输入创建许多函数 我正在考虑使用 switch 创建单个函数 使用 switch 创建单一函数是个好主意吗 如何更新对象的
  • Redux 状态 - 最大推荐大小

    我正在构建一个项目管理应用程序 但我仍然在努力解决一个问题 在 Redux 状态中存储什么以及 按需 加载什么 Redux 状态是否有已知的 最大推荐大小 几十千字节 几百千字节 兆字节单位 请记住 归根结底 Redux 只是 如何 将信息
  • 如何在 React 中将图像加载到画布上?

    如何在 React 中将图像上传到画布的整个宽度 高度 例如 class PlanPage extends Component constructor props super props componentWillMount this se
  • 内的 ref={register} 给了我一个 path.split 错误

    嗨 我正在尝试用 React 制作一个表单 当我输入 ref register inside
  • AppBar 在 React 路由之间消失

    我有一个几乎可以工作的简单反应路由项目 我有一个AppBar and a Drawer 使用穆伊 抽屉里有三个项目 它们将重新路由应用程序 我的路由工作正常 但我遇到的问题是AppBar 因此一旦您进入某个页面 应用程序其余部分的导航就不再
  • 如何在反应中循环

    如何循环重复 这是我的代码 这是App js import React from react import App css import Child from Child Child function App return div div
  • React 文件预览 (FIREBASE)

    我目前将文件存储在 Firebase 存储中 我希望能够实时生成每个文件的文件预览 映射 例如 PDF 文件会将第一页显示为图像 docx 将是文档的第一页 pptx 将是第一张幻灯片 未知文档将是默认文档符号 有人知道有什么好的服务可以轻
  • 在玩笑中运行普通转换后如何转换模块

    用笑话测试 React 组件 其中一些组件使用 OpenLayers ol 软件包 v5 2 0 在 ol 包 v4 中 我应用了 transformIgnorePatterns 来转换 ol 包 jest transformIgnoreP
  • 如何将背景图像仅应用于一个反应页面而不是整个应用程序?

    注册页面示例 register background image linear gradient to right ff5722 0 ff9800 100 margin top 150px important div div div div
  • firebase :: 无法读取 null 的属性“props”

    你好 我正在尝试将react router与firebase一起使用 但它给了我这个错误 无法读取 null 的属性 props 这正是代码 我正在其中使用我的反应路由器 向下代码位于作为登录面板组件的组件上 else if this em
  • 如何在 Swiper 实例之外使用 useSwiper?

    我在用着用于 React 的滑动器 https swiperjs com react显示一些幻灯片 我一直坚持使用外部按钮在幻灯片 上一张和下一张 之间导航 刷卡器有一个useSwiper hook https swiperjs com r

随机推荐

  • 设计模式-建造者模式

    文章目录 建造者模式 创建复杂对象的优雅方式 什么是建造者模式 建造者模式的使用场景 优缺点 示例 使用建造者模式创建电脑对象 建造者模式 创建复杂对象的优雅方式 在软件开发中 有时候我们需要创建具有复杂结构和多个组件的对象 直接在客户端代
  • 【Pytorch异常笔记】Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized.

    文章目录 异常描述 解决方法 开发环境 异常描述 OMP Error 15 Initializing libiomp5md dll but found libiomp5md dll already initialized OMP Hint
  • git 和远程服务器关联,Git远程服务器连接被拒绝

    我对这个错误感到疯狂 2天后我没有发现我的系统出了什么问题 我相信修复它非常容易 Git远程服务器连接被拒绝 当我尝试使用Git功能我得到的消息 连接到我的git服务器 无法打开连接 主机不存在 致命的 无法从远程存储库中读取 请确保您拥有
  • Android JNI1--C语言基础

    1 include 相当于java的导包操作 例如 include
  • vue中computed的属性对data中的属性赋值为undefined的原因

    场景 我在computed中return了一个值 然后在data中直接将它复制给另一个属性 结果data中的属性值为undefined 代码示例 timer为undefined 原因 在这里很容易想到是执行顺序的问题 computed中的属
  • sql server 中的日期计算,如当天周的第一天,当前月的第一天

    根据给定的日期 计算该日期在本月所在周数 每周的第一天为周日 但是在月末需要与下个月进行衔接 如 图2012年2月份 3月份的1 2 3号为2月份的第4周 而2月份的1 2 3 4为1月份的最后一周 第五周 declare datetime
  • linux线程间通讯----管道、信号

    进程间通讯机制 unix继承 管道 信号 system V IPC对象 共享内存 消息队列 信号灯集 1 管道 管道分为无名管道和有名管道 区别在于创建的管道是否在文件系统中不可见 无名不可见 有名可见 1 无名管道 特点 1 在创建之后再
  • 系统调用详解

    1 系统调用 1 系统调用就是为了让应用程序可以访问系统资源 每个操作系统都会提供一套接口供应用程序使用 这些接口通常通过中断来实现 例如在windows中是0x2E号中断作为系统调用入口 linux使用0x80号中断作为系统的调用的入口
  • IC数字后端

    在 innovus 里面 有时候我们需要控制 tie cell 的 fanout 和 net length 来避免 tie cell 可能出现 max transition 或者 max fanout 的违例 一般来说 只要 fanout
  • typeScritp的高级函数

    1 交叉类型 交叉类型是将多个类型合并为一个类型 这让我们可以把现有的多种类型叠加到一起成为一种类型 它包含了所需的所有类型的特性 例如 Person Serializable Loggable同时是 Person 和 Serializab
  • BSN唐斯斯:区块链是“新基建中的基建”

    5月16日 19 00 陀螺财经特别邀请到国家信息中心智慧城市发展研究中心副主任 区块链服务网络发展联盟常务副秘书长唐斯斯 为大家带来关于新基建下的区块链新机遇的思考 直播主题 新基建下的区块链新机遇 嘉宾 国家信息中心智慧城市发展研究中心
  • 智慧城管项目可行性研究报告

    后台回复 230915 可获得下载资料的方法 点击文后阅读原文 可获得下载资料的方法 欢迎加入智能交通技术群 联系方式 微信号18515441838
  • C语言中volatile关键字详解以及常见的面试问题

    编译器的优化 程序运行的优化可以分为硬件和软件 硬件上是在CPU和内存中间增加cache 来解决CPU和内存之间运行速率差异过大的问题 软件上则分为编译器优化和程序员优化 程序员优化是程序员在编写代码时 对代码的逻辑顺序进行合理安排 提升效
  • WIN10 修改用户下文件夹的名称

    转载note 我是为了解决正当防卫3不能存档 我的用户名当初设置的数字 转载的原因是 走了很多百度知道和经验的弯路 如果有人看到就别走了 我因为走了弯路前弄后弄导致原先的个人数据文件还丢失 只得跳出步骤新建用户 在PE下复制还有的数据 所以
  • 在windows系统安装linux

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 说明 二 步骤 1 利用WindowsADK安装windows 1 Windows 评估和部署工具包 ADK 包括 CopyPE 和 MakeWinPEM
  • no matching host key type found. Their offer: ssh-rsa 问题解决

    最近升级了Mac OS Ventura 13 0 1后发现ssh指定密钥登录服务器失败 no matching host key type found Their offer ssh rsa 进入当前用户的 ssh目录发现比之前系统多了一个
  • Ubuntu20.04安装qemu错误解决方法

    1 如果运行 configure显示没有找到pip sudo apt get install phthon3 pip 2 如果运行 configure显示 ninja error opening build log Permission d
  • com.mysql.jdbc.Driver 和 com.mysql.cj.jdbc.Driver的区别

    com mysql jdbc Driver 是 mysql connector java 5中的 com mysql cj jdbc Driver 是 mysql connector java 6中的 1 JDBC连接Mysql5 com
  • [共同学习] 平衡二叉树浅见

    平衡二叉树 平衡二叉树的概念 AVL树结点的定义 AVL树的插入 左左 右单旋 右右 左单旋 左右 先左旋 再右旋 右左 先右旋 再左旋 AVL树的验证 验证其为二叉搜索树 验证其为平衡树 AVL树的性能 AVL树的实现 感悟 以上 二叉搜
  • 三分钟实现一个react-router-dom5.0的路由拦截(导航守卫)

    不同于vue 通过在路由里设置meta元字符实现路由拦截 在使用 Vue 框架提供了路由守卫功能 用来在进入某个路有前进行一些校验工作 如果校验失败 就跳转到 404 或者登陆页面 比如 Vue 中的 beforeEnter 函数 rout