问题
有时当用户正在编辑某些内容时(单击了返回按钮),你需要询问他们是否真的想要离开页面。这个看似简单的任务可能很复杂,因为它依赖于能够监测到用户单击浏览器后退按钮,以及能够找到一种方法拦截查看历史记录中的后退操作,并能够取消这一操作(如图2-7所示)。
图2-7:离开当前页面时询问用户是否确认
如果应用程序中有几个页面需要相同的功能,该怎么办?有没有一种简单的方式可以跨组件地实现这一功能?
解决方案
react-router-dom库包含一个名为Prompt的组件,用于询问用户确认他们是否离开页面。
本解决方案唯一需要的工具是react-router-dom库本身:
假设我们在 /important 路由上挂载了一个名为Important的组件,它允许用户编辑一段文本:
Important组件会跟踪确认textarea中的文本是否已经改变了原始值。如果文本发生了变化,那么dirty的值为true。当dirty为ture时,如果用户单击了浏览器后退按钮,我们要如何向用户确认他们是否想要离开页面?
对此,我们添加一个Prompt组件:
如果用户编辑了文本,然后单击后退按钮,Prompt提示窗就会弹出(如图2-8所示)。
图2-8:Prompt提示窗向用户确认他们是否想要离开
添加确认提示窗很容易,但默认提示界面是一个简单的JavaScript对话框。最好是我们自己能够设计这一提示窗。
为了演示如何做到这一点,我们将Material-UI组件库添加到应用程序中:
Material-UI库是基于谷歌的Material Design标准的React实现。我们将用它作为示例,说明如何用更定制化的提示界面替代初始设置。
Prompt组件不渲染任何界面。相反,Prompt组件请求当前Router显示确认信息。默认情况下,BrowserRouter显示默认的JavaScript对话框,但你也可以使用自己的代码实现对话框界面。
使用BrowserRouter时,我们可以向它传递一个名为getUserConfirmation的属性:
getUserConfirmation属性是一个函数,它接受两个参数:它应该显示的消息和一个回调函数。
当用户单击浏览器后退按钮时,Prompt组件将运行getUserConfirmation,然后等待调用回调函数,并传入true或false值。
回调函数会异步地返回用户的选择。当我们询问用户他们想要做什么时,Prompt组件将异步地等待。这允许我们创建一个自定义界面。
让我们创建一个名为Alert的自定义Material-UI对话框。我们将用它代替默认的JavaScript模态对话框来做展示:
当然,我们没有理由指定必须得显示一个对话框,你可以显示一个倒计时计时器或者提示一条消息,又或者直接静默地保存用户的更改。但在本解决方案中,我们将显示一个自定义Alert对话框。
在我们的实现中要如何使用Alert组件呢?首先要做的是创建自己的getUserConfirmation函数。我们会把提示消息和回调函数存下来,然后设置一个布尔值,其值为true表示我们希望打开Alert对话框:
值得注意的是,当存储回调函数时,我们使用的是setConfirmCallback(()=>callback),而不是简单地使用setConfirmCallback(callback)。这是因为useState Hook返回的setter会执行传递给它的函数,而不是将其存起来。
然后,我们可以使用confirmMessage、confirmCallback和confirmOpen的值在界面上渲染Alert。
下面是完整的 App.js 文件:
现在,当用户退出编辑时,他们会看到自定义对话框,如图2-9所示。
图2-9:当用户单击后退按钮时,自定义确认框会弹出
讨论
在本节中,我们使用一个组件库重新实现了Prompt模态对话框,但不要将其局限于把一个对话框替换为另一个对话框这样的场景(这么简单)。如果用户离开页面,你仍可以做一些工作:例如将正在进行的工作存储在某个地方,以便他们以后可以恢复之前的工作环境。getUserConfirmation函数的异步特性支持这种灵活做法。这是react-router-dom抽象出横切关注点的另一个例子。
你可以从GitHub网站( https://oreil.ly/1FyoE )下载本解决方案的源代码。