本文共 12758 字,大约阅读时间需要 42 分钟。
最近接到一个需求,当用户将要离开指定页面的时候,需要拦截页面的跳转,并弹出提示框,提醒用户去做某一项操作(比如数据的保存和提交),只有当用户完成操作之后,或者关闭提示窗,才能离开此页面进行下一页面的跳转。
这样的需求,通常做法是:监听路由的跳转操作,阻塞跳转,实现拦截,并在用户处理完需要的操作之后(如数据保存、提交、关闭弹窗),才放开跳转权限,允许用户跳转下一页面。如果前端项目是使用vue来编写,我们可以很快处理这一问题,因为vue自带的导航守卫Api如beforeRouteLeave就可以实现该功能。
但是react并没有提供像vue一样的导航守卫Api,因此我们需要另辟蹊径。react-router-dom提供了Prompt组件,通过在需要进行路由跳转拦截的页面的任意地方加上Prompt组件,我们都能实现路由跳转拦截。
{ 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组件
接下来我们看看在react中如何使用Prompt组件实现路由拦截:
页面上任意位置挂载Prompt 组件,点击“路由跳转”按钮,触发路由跳转事件import React, { Component } from 'react';import { Button } from 'antd';import { withRouter, Prompt } from 'react-router-dom'; // 从react-router-dom中引出Prompt组件class index extends Component { state = { } // 跳转路由 handleRouterSwitch = () => { this.props.history.push('/outside') } render() { return ({ /* 页面的任何地方加上Prompt组件都生效 */}) }}export default withRouter(index)
接下来我们看看Prompt组件的when和message属性的不同属性值,在触发路由跳转时,会发生什么:
① 只赋值when属性,不赋值message属性,页面会报错,提示message属性应必传
④ when=true,message为方法,且方法返回值为字符串,当触发路由跳转事件时,路由跳转会被拦截,并弹出提示窗,提示信息为message的方法返回的字符串,点击提示窗确定按钮,路由被释放,跳转到指定页面;点击取消按钮,路由跳转被取消,同时关闭提示窗
{ return '信息还没保存,确定离开吗?'}} />
{ console.log(location, '跳转'); return '信息还没保存,确定离开吗?';}} />
⑥ when=true,message为方法,且方法返回值为true,同时message方法内还包含如console.log(‘跳转’)语句。当触发路由跳转事件时,message方法会被执行,路由跳转不会被拦截,路由会跳转到指定页面。
{ console.log(location, '跳转'); return true;}} />
{ console.log(location, '跳转'); return false;}} />
import React, { Component } from 'react';import { Button } from 'antd';import { withRouter, Prompt } from 'react-router-dom'; // 从react-router-dom中引出Prompt组件class index extends Component { 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 '信息还没保存,确定离开吗?' } else if (location.pathname.indexOf('errorUrl') > -1) { // 阻塞跳转到特定的页面,并弹出提示弹窗 return '禁止跳转到指定的errorUrl页面,确定继续跳转吗' } else { return true; // 符合跳转的条件,释放路由,路由正常跳转 } } render() { const { isHoldUpRouter } = this.state; return ({ /* 页面的任何地方加上Prompt组件都生效 */}) }}export default withRouter(index);
效果:
当用户点击“路由跳转”按钮,阻塞路由跳转,弹出提示窗,提示内容为message的函数返回的字符串,当用户点击“保存信息”按钮后,再次点击路由跳转按钮,路由成功跳转,不再弹出提示窗从上述示例我们可以看到,Prompt阻塞路由跳转之后弹出的提示弹窗,样式是系统默认的,我们并不可以修改,那么我们能不能自定义提示弹窗呢?接下来我们演示一下如何自定义Prompt的提示弹窗
需求:import React, { 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'class index extends Component { 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; const pathUrl = 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, }) }); } return false; } // 处理保存信息提示弹窗的确认事件 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 ({ /* 页面的任何地方加上Prompt组件都生效 */}) }}export default withRouter(index);信息还没保存,确定离开吗?
我们可用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的路由跳转拦截作用
接下来我们看看在react中如何使用history.block实现路由拦截:
在组件的componentDidMount生命周期中初始化history.block,其回调函数返回字符串,这会使得在该页面做路由跳转的时候会被拦截,并弹出提示窗,提示信息为其回调函数返回的字符串在组件的componentWillUnmount这一生命周期对history.block重新初始化,让其回调函数return true,取消history.block的路由跳转拦截作用,防止其影响其他页面做路由跳转
import React, { Component } from 'react';import { Button } from 'antd';import { withRouter } from 'react-router-dom';class index extends Component { 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参数包含即将要跳转到指定路径的路由信息 return true; // return false; // return '信息还没保存,确定离开吗?' }); } // 跳转路由 handleRouterSwitch = () => { this.props.history.push('/outside') } render() { return () }}export default withRouter(index);
点击“路由跳转”按钮,触发路由跳转事件,history.block阻塞路由跳转,并弹出提示窗,提示信息为history.block回调函数return的字符串
点击弹窗“确定”按钮,释放路由,继续跳转到目标url
点击弹窗“取消”按钮,关闭弹窗,路由不做跳转从上述示例我们可以看到,history.block阻塞路由跳转之后弹出的提示弹窗,样式是系统默认的,我们并不可以修改,那么我们能不能自定义提示弹窗呢?接下来我们演示一下如何自定义history.block的提示弹窗
需求:做法:
history.block只有在其回调函数返回值为字符串的时候,才会弹出系统弹窗,而返回布尔值的时候,不会弹出弹窗,我们仅仅需要history.block的路由拦截功能,不让其弹出系统弹窗,而是弹出自定义弹窗,那么我们让history.block的回调函数返回true就可以释放路由,返回false就可以拦截路由,在返回false的同时,展示自定义的弹窗。通过重新初始化history.block,操作history.block的回调函数返回值,来拦截或者释放路由的跳转。import React, { Component } from 'react';import { Button, Modal } from 'antd';import { withRouter } from 'react-router-dom';import { ExclamationCircleOutlined } from '@ant-design/icons'class index extends Component { 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参数包含即将要跳转到指定路径的路由信息 return true; // 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; const pathUrl = location.pathname + location.search // // location 携带的路径,即将要跳转的路径 this.setState({ whichPathUrlWillTo: pathUrl, // 存储即将要跳转的pathUrl }) if (!isUserInfoSaved) { // 信息未保存,阻塞路由跳转,并弹出自定义提示弹窗 this.setState({ isShowSavePromptModal: true, }); } else { this.props.history.block(location => { // 重新初始化history.block,释放路由跳转权限 return true; }); this.props.history.push(pathUrl) // 手动跳转 } return false; } // 处理保存信息提示弹窗的确认事件 handleSaveModelOK = () => { this.setState({ isShowSavePromptModal: false, // 关闭自定义提示窗 }) this.props.history.block(location => { // 重新初始化history.block,释放路由跳转权限 return true; }); this.props.history.push(this.state.whichPathUrlWillTo) // 手动跳转 } // 处理保存信息提示弹窗的取消事件 handleSaveModelCancel = () => { this.setState({ isShowSavePromptModal: false, // 关闭自定义提示窗 }) } render() { const { isShowSavePromptModal } = this.state; return () }}export default withRouter(index);信息还没保存,确定离开吗?
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