react实现路由跳转拦截功能(导航守卫)

简介: react实现路由跳转拦截功能(导航守卫)

背景

最近接到一个需求,当用户将要离开指定页面的时候,需要拦截页面的跳转,并弹出提示框,提醒用户去做某一项操作(比如数据的保存和提交),只有当用户完成操作之后,或者关闭提示窗,才能离开此页面进行下一页面的跳转。

这样的需求,通常做法是:监听路由的跳转操作,阻塞跳转,实现拦截,并在用户处理完需要的操作之后(如数据保存、提交、关闭弹窗),才放开跳转权限,允许用户跳转下一页面。


方法1:通过Prompt组件实现react路由跳转拦截功能

如果前端项目是使用vue来编写,我们可以很快处理这一问题,因为vue自带的导航守卫Api如beforeRouteLeave就可以实现该功能。


但是react并没有提供像vue一样的导航守卫Api,因此我们需要另辟蹊径。react-router-dom提供了Prompt组件,通过在需要进行路由跳转拦截的页面的任意地方加上Prompt组件,我们都能实现路由跳转拦截。


Prompt组件介绍

<Promptwhen={true} message={(location) => { return'信息还没保存,确定离开吗?' }} />

Prompt接受两个属性:


when(非必传),数据类型为boolean值。

when=true时,阻塞路由跳转(如果message为字符串或者message的方法返回字符串,还会弹出提示弹窗,弹窗信息为message的字符串值或者message方法返回的字符串);

when=false,不阻塞路由跳转,也不弹出提示弹窗(当when=false时,即使message为字符串或者message的方法返回字符串,也不会弹出提示窗)。

注意:只有当when=true的时候,才会执行message的方法。


message(必传),数据类型可以是字符串或方法

message可以是字符串或方法,如果message为方法,那么该方法会接收一个location参数,参数包含将要跳转的下一个路由的路径等信息(如果message为方法,那么该方法主要用于处理路由拦截后的操作)。

前提是当when=true时,message为字符串,则会阻塞路由并弹出提示弹窗,弹窗内容为message字符串。

前提是当when=true时,message为方法,当方法返回true就顺利跳转;返回false则阻塞路由跳转(不会弹出提示弹窗);当方法返回字符串就阻塞路由跳转,并弹出提示弹窗,弹窗提示内容为方法返回的字符串。


Prompt对路由拦截的作用只会作用于其所挂载的当前路由,当跳转到另一个路由(或者Prompt组件被销毁的时候),该Prompt组件将不会再对路由跳转有拦截作用,除非重新挂载和初始化Prompt组件


Prompt组件示例

接下来我们看看在react中如何使用Prompt组件实现路由拦截:

页面上任意位置挂载Prompt 组件,点击“路由跳转”按钮,触发路由跳转事件

importReact, { Component } from'react';
import { Button } from'antd';
import { withRouter, Prompt } from'react-router-dom'; // 从react-router-dom中引出Prompt组件classindexextendsComponent {
state= {}
// 跳转路由handleRouterSwitch= () => {
this.props.history.push('/outside')
  }
render() {
return (
<div><ButtononClick={this.handleRouterSwitch}>跳转路由</Button>        {/* 页面的任何地方加上Prompt组件都生效 */}
<Promptwhen={true} message="信息还没保存,确定离开吗?"/></div>    )
  }
}
exportdefaultwithRouter(index)

接下来我们看看Prompt组件的when和message属性的不同属性值,在触发路由跳转时,会发生什么:

① 只赋值when属性,不赋值message属性,页面会报错,提示message属性应必传

<Promptwhen={true} />

image.png

② when=true,message为字符串,当触发路由跳转事件时,路由跳转会被拦截,并弹出提示窗,提示信息为message的值,点击提示窗确定按钮,路由被释放,跳转到指定页面;点击取消按钮,路由跳转被取消,同时关闭提示窗

<Promptwhen={true} message="信息还没保存,确定离开吗?"/>

image.png

③ when=false,message为字符串,当触发路由跳转事件时,路由跳转不会被拦截,路由会跳转到指定页面。这是因为只有当when=true的时候,才会执行message的方法。

<Promptwhen={false} message="信息还没保存,确定离开吗?"/>

④ when=true,message为方法,且方法返回值为字符串,当触发路由跳转事件时,路由跳转会被拦截,并弹出提示窗,提示信息为message的方法返回的字符串,点击提示窗确定按钮,路由被释放,跳转到指定页面;点击取消按钮,路由跳转被取消,同时关闭提示窗

<Promptwhen={true} message={(location) => {return'信息还没保存,确定离开吗?'}} />

image.png

⑤ when=false,message为方法,且方法返回值为字符串,同时message方法内还包含如console.log(‘跳转’)语句。当触发路由跳转事件时,路由跳转不会被拦截,路由会跳转到指定页面。且message方法未被执行。这是因为只有当when=true的时候,才会执行message的方法。

<Promptwhen={false} message={(location) => {
console.log(location, '跳转');
return'信息还没保存,确定离开吗?';
}} />

⑥ when=true,message为方法,且方法返回值为true,同时message方法内还包含如console.log(‘跳转’)语句。当触发路由跳转事件时,message方法会被执行,路由跳转不会被拦截,路由会跳转到指定页面。

<Promptwhen={true} message={(location) => {
console.log(location, '跳转');
returntrue;
}} />

image.png

⑦ when=true,message为方法,且方法返回值为false,同时message方法内还包含如console.log(‘跳转’)语句。当触发路由跳转事件时,message方法会被执行,路由跳转会被拦截,但不会弹出提示框。

<Promptwhen={true} message={(location) => {
console.log(location, '跳转');
returnfalse;
}} />

image.png

⑧ 接下来我们演示一下项目中通过Prompt组件拦截用户跳转路由。

需求:


用户跳转页面的时候,判断用户是否保存了用户数据,如果没有,则阻塞跳转,弹出确认提示。

用户跳转页面的时候,判断用户是否是跳转了不让跳转的特殊页面,如果没有,则阻塞跳转,弹出确认提示。

做法:

Prompt的when属性值变量放于state中,message赋值为方法

注意:

当when的属性值,初始化必须为true,否则阻塞不了路由跳转。因为setState是异步操作

importReact, { Component } from'react';
import { Button } from'antd';
import { withRouter, Prompt } from'react-router-dom'; // 从react-router-dom中引出Prompt组件classindexextendsComponent {
state= {
isUserInfoSaved: false,
isHoldUpRouter: true, // when的属性值,初始化必须为true,否则阻塞不了路由跳转。因为setState是异步操作  }
// 跳转路由handleRouterSwitch= () => {
this.props.history.push('/settings/audit_rules')
  }
// 保存用户信息saveUserInfo= () => {
this.setState({
isUserInfoSaved: true    })
  }
// 处理路由拦截handleRouterHoldUp= (location) => {
const { isUserInfoSaved } =this.state;
if (!isUserInfoSaved) { // 信息未保存,阻塞路由跳转,并弹出提示弹窗return'信息还没保存,确定离开吗?'    } elseif (location.pathname.indexOf('errorUrl') >-1) { // 阻塞跳转到特定的页面,并弹出提示弹窗return'禁止跳转到指定的errorUrl页面,确定继续跳转吗'    } else {
returntrue; // 符合跳转的条件,释放路由,路由正常跳转    }
  }
render() {
const { isHoldUpRouter } =this.state;
return (
<div><ButtononClick={this.handleRouterSwitch}>跳转路由</Button><ButtononClick={this.saveUserInfo}>保存信息</Button>        {/* 页面的任何地方加上Prompt组件都生效 */}
<Promptwhen={isHoldUpRouter} message={this.handleRouterHoldUp} /></div>    )
  }
}
exportdefaultwithRouter(index);

效果:

当用户点击“路由跳转”按钮,阻塞路由跳转,弹出提示窗,提示内容为message的函数返回的字符串,当用户点击“保存信息”按钮后,再次点击路由跳转按钮,路由成功跳转,不再弹出提示窗

image.png

自定义Prompt组件的提示弹窗

从上述示例我们可以看到,Prompt阻塞路由跳转之后弹出的提示弹窗,样式是系统默认的,我们并不可以修改,那么我们能不能自定义提示弹窗呢?接下来我们演示一下如何自定义Prompt的提示弹窗

需求:


用户跳转页面的时候,判断用户是否保存了用户数据,如果没有,则阻塞跳转,弹出确认提示;如果已保存,则正常跳转路由。

确认提示框使用自定义提示框

点击弹窗“取消”按钮,关闭弹窗,不跳转页面;点击弹窗“确定”按钮,关闭弹窗,跳转页面;

做法:

Prompt的when属性值变量放于state中,message赋值为方法。其中message方法主要用于处理路由拦截后的操作,为了不让默认的系统弹窗弹出,而是弹出我们自己的自定义弹窗,因此这时候message方法只能始终return false。这样的话,我们主要操作Prompt的when属性值变化,来拦截或者释放路由的跳转。

注意:

当when的属性值,初始化必须为true,否则阻塞不了路由跳转,因为setState是异步操作;同时,当setState改变when的属性值之后,因为setState是异步操作,需要在setState的回调上手动跳转指定页面的路由,这样才能在处理完业务逻辑之后,立马同步跳转路由,否则由于是异步更新state,用户需要点击两次跳转按钮才能跳转路由。

importReact, { Component } from'react';
import { Button, Modal } from'antd';
import { withRouter, Prompt } from'react-router-dom'; // 从react-router-dom中引出Prompt组件import { ExclamationCircleOutlined } from'@ant-design/icons'classindexextendsComponent {
state= {
isUserInfoSaved: false,
isHoldUpRouter: true, // when的属性值,初始化必须为true,否则阻塞不了路由跳转。因为setState是异步操作whichPathUrlWillTo: '',
isShowSavePromptModal: false,
  }
// 跳转路由handleRouterSwitch= () => {
this.props.history.push('/settings/audit_rules')
  }
// 保存用户信息saveUserInfo= () => {
this.setState({
isUserInfoSaved: true,
    })
  }
// 处理路由拦截handleRouterHoldUp= (location) => {
console.log(location)
const { isUserInfoSaved } =this.state;
constpathUrl=location.pathname+location.search// // location 携带的路径,即将要跳转的路径this.setState({
whichPathUrlWillTo: pathUrl, // 存储即将要跳转的pathUrl    })
if (!isUserInfoSaved) { // 信息未保存,阻塞路由跳转,并弹出自定义提示弹窗this.setState({
isShowSavePromptModal: true,
      });
    } else {
this.setState({ // 释放路由跳转权限isHoldUpRouter: false      }, () => {
this.props.history.push(pathUrl) // 手动跳转,如果是手动跳转,必须放在这里执行,因为setState是异步的,如果不放回调里执行手动跳转,会陷入Prompt组件的死循环this.setState({ // 内部路由变化,当跳转之后,还需要重新关闭路由跳转权限,实现下一次跳转路由的拦截(如果所在页面的组件已经完全销毁,则不需要重新关闭路由跳转权限)isHoldUpRouter: true,
        })
      });
    }
returnfalse;
  }
// 处理保存信息提示弹窗的确认事件handleSaveModelOK= () => {
this.setState({
isShowSavePromptModal: false, // 关闭自定义提示窗isHoldUpRouter: false, // 释放路由跳转权限    },() => {
this.props.history.push(this.state.whichPathUrlWillTo) // 手动跳转,如果是手动跳转,必须放在这里执行,因为setState是异步的,如果不放回调里执行手动跳转,会陷入Prompt组件的死循环this.setState({ // 内部路由变化,当跳转之后,还需要重新关闭路由跳转权限,实现下一次跳转路由的拦截(如果所在页面的组件已经完全销毁,则不需要重新关闭路由跳转权限)isHoldUpRouter: true,
      })
    })
  }
// 处理保存信息提示弹窗的取消事件handleSaveModelCancel= () => {
this.setState({
isShowSavePromptModal: false, // 关闭自定义提示窗    })
  }
render() {
const { isHoldUpRouter, isShowSavePromptModal } =this.state;
return (
<div><ButtononClick={this.handleRouterSwitch}>跳转路由</Button><ButtononClick={this.saveUserInfo}>保存信息</Button>        {/* 页面的任何地方加上Prompt组件都生效 */}
<Promptwhen={isHoldUpRouter} message={this.handleRouterHoldUp} /><Modaltitle="提示"closable={false} visible={isShowSavePromptModal} onOk={this.handleSaveModelOK} onCancel={this.handleSaveModelCancel}><divstyle={{fontSize: '14px'}}><ExclamationCircleOutlined/><spanstyle={{marginLeft: '10px'}}>信息还没保存,确定离开吗?</span></div></Modal></div>    )
  }
}
exportdefaultwithRouter(index);

image.png

方法2:通过history.block实现react路由跳转拦截功能

history.block介绍

我们可用withrouter把histroy注入props,用history.block阻塞路由跳转。


当history.block的回调函数返回true,则释放路由跳转;

当history.block的回调函数返回false,则阻塞路由跳转,不弹出弹窗;

当history.block的回调函数返回字符串,则阻塞路由跳转,弹出弹窗,弹窗提示信息为回调函数的返回字符串


history.block的回调函数接受location参数,location参数包含即将要跳转到指定路径的路由信息


千万要注意的是:history.block的作用对项目是全局影响的,只要history.block初始化一次,就会对所有的路由跳转做拦截,即使跳出了当前路由,在另一个路由做跳转的时候,history.block依旧会生效,并起到路由拦截作用。如果想取消history.block的路由跳转拦截作用,只有对其重新初始化,让其回调函数return true,所以通常做法是,在组件的componentWillUnmount这一生命周期对history.block重新初始化,让其回调函数return true,取消history.block的路由跳转拦截作用


history.block基本示例

接下来我们看看在react中如何使用history.block实现路由拦截:

在组件的componentDidMount生命周期中初始化history.block,其回调函数返回字符串,这会使得在该页面做路由跳转的时候会被拦截,并弹出提示窗,提示信息为其回调函数返回的字符串


在组件的componentWillUnmount这一生命周期对history.block重新初始化,让其回调函数return true,取消history.block的路由跳转拦截作用,防止其影响其他页面做路由跳转

importReact, { Component } from'react';
import { Button } from'antd';
import { withRouter } from'react-router-dom';
classindexextendsComponent {
state= {}
componentDidMount() {
this.props.history.block(location=> { // 当history.block的回调函数返回true,则释放路由跳转;当history.block的回调函数返回false,则阻塞路由跳转,不弹出弹窗;当history.block的回调函数返回字符串,则阻塞路由跳转,弹出弹窗,弹窗提示信息为回调函数的返回字符串(点击确定,释放路由,继续跳转到指定页面,点击取消,关闭弹窗,继续阻塞路由跳转)console.log(location) // history.block的回调函数接手location参数,location参数包含即将要跳转到指定路径的路由信息// return true;// return false;return'信息还没保存,确定离开吗?'    });
  }
componentWillUnmount() { // history.block的作用对项目是全局影响的,组件的componentWillUnmount一定要记得重新初始化history.block,让其回调函数返回true,取消history.block的路由跳转拦截作用,防止其影响其他页面做路由跳转this.props.history.block(location=> { // 当history.block的回调函数返回true,则释放路由跳转;当history.block的回调函数返回false,则阻塞路由跳转,不弹出弹窗;当history.block的回调函数返回字符串,则阻塞路由跳转,弹出弹窗,弹窗提示信息为回调函数的返回字符串(点击确定,释放路由,继续跳转到指定页面,点击取消,关闭弹窗,继续阻塞路由跳转)console.log(location) // history.block的回调函数接手location参数,location参数包含即将要跳转到指定路径的路由信息returntrue;
// return false;// return '信息还没保存,确定离开吗?'    });
  }
// 跳转路由handleRouterSwitch= () => {
this.props.history.push('/outside')
  }
render() {
return (
<div><ButtononClick={this.handleRouterSwitch}>跳转路由</Button></div>    )
  }
}
exportdefaultwithRouter(index);

点击“路由跳转”按钮,触发路由跳转事件,history.block阻塞路由跳转,并弹出提示窗,提示信息为history.block回调函数return的字符串

点击弹窗“确定”按钮,释放路由,继续跳转到目标url

点击弹窗“取消”按钮,关闭弹窗,路由不做跳转

image.png

history.block使用示例(自定义弹窗)

从上述示例我们可以看到,history.block阻塞路由跳转之后弹出的提示弹窗,样式是系统默认的,我们并不可以修改,那么我们能不能自定义提示弹窗呢?接下来我们演示一下如何自定义history.block的提示弹窗

需求:


用户跳转页面的时候,判断用户是否保存了用户数据,如果没有,则阻塞跳转,弹出确认提示;如果已保存,则正常跳转路由。

确认提示框使用自定义提示框

点击弹窗“取消”按钮,关闭弹窗,不跳转页面;点击弹窗“确定”按钮,关闭弹窗,跳转页面;

做法:

history.block只有在其回调函数返回值为字符串的时候,才会弹出系统弹窗,而返回布尔值的时候,不会弹出弹窗,我们仅仅需要history.block的路由拦截功能,不让其弹出系统弹窗,而是弹出自定义弹窗,那么我们让history.block的回调函数返回true就可以释放路由,返回false就可以拦截路由,在返回false的同时,展示自定义的弹窗。通过重新初始化history.block,操作history.block的回调函数返回值,来拦截或者释放路由的跳转。

importReact, { Component } from'react';
import { Button, Modal } from'antd';
import { withRouter } from'react-router-dom';
import { ExclamationCircleOutlined } from'@ant-design/icons'classindexextendsComponent {
state= {
isUserInfoSaved: false,
whichPathUrlWillTo: '',
isShowSavePromptModal: false,
  }
componentDidMount() {
this.props.history.block(this.handleRouterHoldUp);
  }
componentWillUnmount() { // history.block的作用对项目是全局影响的,组件的componentWillUnmount一定要记得重新初始化history.block,让其回调函数返回true,取消history.block的路由跳转拦截作用,防止其影响其他页面做路由跳转this.props.history.block(location=> { // 当history.block的回调函数返回true,则释放路由跳转;当history.block的回调函数返回false,则阻塞路由跳转,不弹出弹窗;当history.block的回调函数返回字符串,则阻塞路由跳转,弹出弹窗,弹窗提示信息为回调函数的返回字符串(点击确定,释放路由,继续跳转到指定页面,点击取消,关闭弹窗,继续阻塞路由跳转)console.log(location) // history.block的回调函数接手location参数,location参数包含即将要跳转到指定路径的路由信息returntrue;
// return false;// return '信息还没保存,确定离开吗?'    });
  }
// 跳转路由handleRouterSwitch= () => {
this.props.history.push('/machine_learning/permission_settings')
  }
// 保存用户信息saveUserInfo= () => {
this.setState({
isUserInfoSaved: true,
    })
  }
// 处理路由拦截handleRouterHoldUp= (location) => {
console.log(location)
const { isUserInfoSaved } =this.state;
constpathUrl=location.pathname+location.search// // location 携带的路径,即将要跳转的路径this.setState({
whichPathUrlWillTo: pathUrl, // 存储即将要跳转的pathUrl    })
if (!isUserInfoSaved) { // 信息未保存,阻塞路由跳转,并弹出自定义提示弹窗this.setState({
isShowSavePromptModal: true,
      });
    } else {
this.props.history.block(location=> { // 重新初始化history.block,释放路由跳转权限returntrue;
      });
this.props.history.push(pathUrl) // 手动跳转    }
returnfalse;
  }
// 处理保存信息提示弹窗的确认事件handleSaveModelOK= () => {
this.setState({
isShowSavePromptModal: false, // 关闭自定义提示窗    })
this.props.history.block(location=> { // 重新初始化history.block,释放路由跳转权限returntrue;
    });
this.props.history.push(this.state.whichPathUrlWillTo) // 手动跳转  }
// 处理保存信息提示弹窗的取消事件handleSaveModelCancel= () => {
this.setState({
isShowSavePromptModal: false, // 关闭自定义提示窗    })
  }
render() {
const { isShowSavePromptModal } =this.state;
return (
<div><ButtononClick={this.handleRouterSwitch}>跳转路由</Button><ButtononClick={this.saveUserInfo}>保存信息</Button><Modaltitle="提示"closable={false} visible={isShowSavePromptModal} onOk={this.handleSaveModelOK} onCancel={this.handleSaveModelCancel}><divstyle={{fontSize: '14px'}}><ExclamationCircleOutlined/><spanstyle={{marginLeft: '10px'}}>信息还没保存,确定离开吗?</span></div></Modal></div>    )
  }
}
exportdefaultwithRouter(index);

image.png

https://www.cnblogs.com/tirybk/p/14688240.html

https://www.cnblogs.com/amiezhang/p/13207409.html

https://github.com/mirrorjs/mirror/issues/78

https://segmentfault.com/a/1190000019105896

https://segmentfault.com/a/1190000020241389?utm_source=tag-newest

https://segmentfault.com/a/1190000039190541

目录
相关文章
|
7天前
|
前端开发 JavaScript
React项目路由懒加载lazy、Suspense,使第一次打开项目页面变快
本文介绍了在React项目中实现路由懒加载的方法,使用React提供的`lazy`和`Suspense`来优化项目首次加载的速度。通过将路由组件改为懒加载的方式,可以显著减少初始包的大小,从而加快首次加载速度。文章还展示了如何使用`Suspense`组件包裹`Switch`来实现懒加载过程中的fallback效果,并提供了使用前后的加载时间对比,说明了懒加载对性能的提升作用。
24 2
React项目路由懒加载lazy、Suspense,使第一次打开项目页面变快
|
7天前
|
移动开发 前端开发
react项目配合diff实现文件对比差异功能
在React项目中,可以使用`diff`库实现文件内容对比差异功能。首先安装`diff`库,然后在组件中引入并使用`Diff.diffChars`或`Diff.diffLines`方法比较文本差异。通过循环遍历`diff`结果,可以生成不同样式的HTML元素来高亮显示文本差异。
18 1
react项目配合diff实现文件对比差异功能
|
29天前
|
前端开发 Python
React技术栈-React路由插件之自定义组件标签
关于React技术栈中React路由插件自定义组件标签的教程。
44 4
React技术栈-React路由插件之自定义组件标签
|
7天前
|
前端开发 JavaScript
React配合axios请求拦截校验session,403跳转至登陆页面
React中使用axios进行请求拦截,通过自定义事件监听和响应拦截实现403状态码时的自动登录页面跳转。
15 2
|
8天前
|
前端开发
React 中购物车功能实现(全选多选功能实现)
React 中购物车功能实现(全选多选功能实现)
|
18天前
|
移动开发 前端开发 应用服务中间件
React两种路由模式的实现原理
React两种路由模式的实现原理
37 3
|
29天前
|
前端开发 程序员 API
React技术栈-React路由插件之react-router的基本使用
这篇博客介绍了React路由插件react-router的基本使用,包括其概念、API、以及如何通过实战案例在React应用中实现SPA(单页Web应用)的路由管理。
40 9
|
4天前
|
存储 移动开发 前端开发
初探react,用react实现一个todoList功能
该文章通过创建一个TodoList应用来介绍React的基础知识,包括环境搭建、组件创建、状态管理和事件处理,并演示了如何使用React Hooks来优化组件逻辑。
|
2月前
|
前端开发 测试技术 开发者
React Router的神奇之处:如何用导航与路由管理让你的复杂SPA飞起来?
【8月更文挑战第31天】本文全面解析了React Router——一款用于React应用的路由与导航管理库。通过定义不同路径并依据URL渲染组件,React Router支持路径匹配、参数路由及嵌套路由等多种模式。文章详细介绍了其基本与高级用法,如使用`Link`组件导航、`Switch`组件进行路径匹配及`NavLink`自定义活动链接样式。此外,还探讨了懒加载、代码分割等性能优化技巧,并提供了简单示例代码,帮助读者快速上手。遵循本文最佳实践,开发者能够更高效地利用React Router构建复杂的单页面应用。
36 0
|
2月前
|
存储 JavaScript 前端开发
探索React状态管理:Redux的严格与功能、MobX的简洁与直观、Context API的原生与易用——详细对比及应用案例分析
【8月更文挑战第31天】在React开发中,状态管理对于构建大型应用至关重要。本文将探讨三种主流状态管理方案:Redux、MobX和Context API。Redux采用单一存储模型,提供预测性状态更新;MobX利用装饰器语法,使状态修改更直观;Context API则允许跨组件状态共享,无需第三方库。每种方案各具特色,适用于不同场景,选择合适的工具能让React应用更加高效有序。
43 0

热门文章

最新文章