React中离开页面时,显示自定义弹框提示数据清除的实现

现在网站中,涉及到表单填写之类的数据编辑,当用户想主观想要离开这个页面,或者刷新、关闭页面、不小心触发了路由跳转的操作时,都会给用户一个友好的提示,避免这种关闭导致的数据编辑丢失。

浏览器刷新、关闭页面时,默认有一个弹框提示;但是如果离开这个页面,需要展示我们自定义的提示呢?例如这种:

接下来我们就来说说如何实现离开页面时,给用户友好提示数据清除的情况。

实现这个功能,需要做两个事:

  1. 路由组件之间切换跳转,导致卸载组件、路由变化
  2. 浏览器刷新、关闭,导致触发页面卸载

一、处理路由组件之间切换跳转:

Vue 导航守卫实现

用过 Vue 的都会觉得这个实现起来很简单吧?利用 Vue-route 的导航守卫,根据 to、from 或者一些其他的条件,很方便就能实现路由跳转的控制,展示我们自定义的弹框。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
beforeRouteLeave (to, from, next) {
if (flag) {
// elementui的confirm
this.$confirm('离开页面,数据会清除...', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
next()
})
.catch(() => {
// 浏览器前进后退
window.history.go(1)
})
} else {
next()
}
}

然而,我们现在要说的是 用 react 实现!

componentWillUnmount 实现可以吗?

componentWillUnmount 这个钩子函数,组件卸载前的回调,它并不能阻止当前组件的卸载。所以不要想方设法在这里做提示,因为即便提示了,组件还是会卸载,数据还是会消失,并且如果这个路由组件比较复杂,由好几个组件组合而成,那么这种想法就不可行了。

所以我们只能从路由层去解决这个问题。

Prompt 实现

1
2
3
4
5
6
7
8
9
10
11
12
import { Prompt } from "react-router-dom";

// 只需把这个组件放在页面中,即可实现路由跳转前的拦截提示

<Prompt
when={isPrompt}
message={(location) =>
location.pathname.startsWith("/app")
? true
: `Are you sure you want to go to ${location.pathname}?`
}
/>;

这种方法适用于 React-Router V4 以上,该组件的参数有:

  1. when(Bool): 是否开启 Prompt 功能。页面没有填写或者编辑的时候,设为 false 就不要离开确认了。
  2. message(string): 设置 Prompt 提示内容
  3. message(function): 返回值为 true/false,即 bool 类型。返回值若为 false 会阻止路由跳转。

React-Router 路由勾子

每个路由都有 Enter 和 Leave 钩子,用户进入或离开该路由时触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
componentDidMount() {
this.props.router.setRouteLeaveHook(
this.props.route[1],
this.routerWillLeave
)
}

routerWillLeave(nextLocation) {
// 该回调函数返回 false 会阻止路由跳转
const { isLeaving } = this.state;
const { pathname } = nextLocation;
// 需要直接跳转的路由
if (/\/test-a/.test(pathname)) {
return true;
}
if (isLeaving) {
return true;
}
this.setState({ isLeaving: true });
this.nextLocation = nextLocation;
return false;
},

如果还是以前 React-Router V2.X 版本的,就适用这个方法。贴上官方英文解释,以免我表达有误,误导人,英文好的可以自行理解:

setRouteLeaveHook(route, hook)
Registers the given hook function to run before leaving the given route.
During a normal transition, the hook function receives the next location as its only argument and can return either a prompt message (string) to show the user, to make sure they want to leave the page; or false, to prevent the transition. Any other return value will have no effect.
During the beforeunload event (in browsers) the hook receives no arguments. In this case it must return a prompt message to prevent the transition.
Returns a function that may be used to unbind the listener.
You don’t need to manually tear down the route leave hook in most cases. We automatically remove all attached route leave hooks after leaving the associated route

setRouteLeaveHook 方法为 Leave 钩子指定 routerWillLeave 函数。该方法需返回 true 或者 false,如果返回 false,将阻止路由的切换,否则继续路由跳转。setRouteLeaveHook 这个方法则返回一个可以用来取消绑定的函数
一般在 componentDidMount 注册 setRouteLeaveHook,这样当 this.props.router.push(‘xxx’)切换路由时,会先执行 routerWillLeave 这个方法,通过返回的 true 还是 false 由它来决定是否跳转。

注意:我用 setRouteLeaveHook 遇到一个坑,第一个参数必须是当前组件的路由,第一个参数传参尝试过 this.props.route, 但是不知道为什么,当时我的这个 this.props.route 对象是从根目录(‘/‘)开始的,即使 componentDidMount 执行了 setRouteLeaveHook,但是注册不成功,根本不会回调 routerWillLeave 这个方法。第一个组件只有是当前路由组件才会注册成功,routerWillLeave 才能在路由变化的时候回调到。

二、处理浏览器刷新、关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
componentDidMount(){
window.onbeforeunload = (e) => {
e.preventDefault();
e.returnValue = '离开当前页后,所编辑的数据将不可恢复' // 浏览器有可能不会提示这个信息,会按照固定信息提示
};
window.onunload = () => {
sessionStorage.setItem('key');
};
}
componentWillUnmount() {
window.onbeforeunload = null;
window.onunload = null;
}

浏览器的刷新和关闭的话,就需要浏览器事件来控制了。

  • beforeunload 当浏览器窗口关闭或者刷新时,会触发 beforeunload 事件。当前页面不会直接关闭,可以点击确定按钮关闭或刷新,也可以取消关闭或刷新
  • unload 当文档或一个子资源正在被卸载时, 触发 unload 事件。该事件会在 beforeunload、pagehide 这两个事件被触发后。

beforeunload 这个事件点击取消或者确定的时候没有回调函数可用,如果我想在确定的时候做一些事情,比如清楚一些缓存,我目前的方法就是在 beforeunload 弹框里点击确定的时候,通过 unload 来实现。