使用 React Router v5 检测页面刷新、标签关闭和路由更改
问题:
解决方案:
想象一下,在填写完一份强制性且无聊的调查问卷后,你不小心关闭了浏览器标签页。你所有的回复现在都丢失了。
真让人沮丧,不是吗?
您肯定不想让您的用户有这样的体验,下面是您可以解决此问题的方法。
问题:
当用户意外...时如何提示用户
- 重新加载页面。
- 关闭浏览器选项卡或窗口。
- 按下浏览器的后退按钮。
- 单击链接/更改路线。
解决方案:
第一部分:检测页面重新加载和浏览器标签页关闭
选项卡/窗口关闭或页面重新加载事件意味着当前文档及其资源将被移除(卸载)。在这种情况下,beforeunload
会触发该事件。
在事件触发时beforeunload
,文档仍然可见,并且事件是可取消的,这意味着unload
可以阻止该事件,就好像它从未发生过一样。
此事件使网页能够触发确认对话框,询问用户是否确实要离开该页面。如果用户确认,浏览器将导航到新页面;否则,浏览器将取消导航。
预防beforeunload
事件
window.onbeforeunload = (event) => {
const e = event || window.event;
// Cancel the event
e.preventDefault();
if (e) {
e.returnValue = ''; // Legacy method for cross browser support
}
return ''; // Legacy method for cross browser support
};
以上3种方法均有效e.preventDefault()
,e.returnValue = ''
并return ''
阻止事件执行。
确认框显示示例:
注意:遗憾的是,并非所有浏览器都支持自定义消息
根据状态显示提示
#1创建一个以 React 状态showExitPrompt
作为参数的函数,并onbeforeunload
在函数内部初始化监听器。在事件监听器内部使用该状态。
为什么要将 React 状态作为参数传递?
因为它onbeforeunload
是一个原生的 JavaScript 事件监听器,任何 React 状态的改变都不会更新其回调函数中的状态。
import { useState } from 'react';
const initBeforeUnLoad = (showExitPrompt) => {
window.onbeforeunload = (event) => {
// Show prompt based on state
if (showExitPrompt) {
const e = event || window.event;
e.preventDefault();
if (e) {
e.returnValue = ''
}
return '';
}
};
};
#2创建状态showExitPrompt
来管理提示并在页面加载时注册事件监听器。
function MyComponent() {
const [showExitPrompt, setShowExitPrompt] = useState(false);
// Initialize the beforeunload event listener after the resources are loaded
window.onload = function() {
initBeforeUnLoad(showExitPrompt);
};
}
#3在状态改变时重新初始化事件监听器。
import { useState, useEffect } from 'react';
const initBeforeUnLoad = (showExitPrompt) => {
// … code
}
function MyComponent() {
const [showExitPrompt, setShowExitPrompt] = useState(false);
window.onload = function() {
initBeforeUnLoad(showExitPrompt);
};
// Re-Initialize the onbeforeunload event listener
useEffect(() => {
initBeforeUnLoad(showExitPrompt);
}, [showExitPrompt]);
}
现在您可以在组件中使用它了。但是,在应用程序的任何地方创建自定义钩子来设置和访问状态更为高效。
使用自定义钩子
#1钩子文件useExitPrompt.js
import { useState, useEffect } from 'react';
const initBeforeUnLoad = (showExitPrompt) => {
window.onbeforeunload = (event) => {
if (showExitPrompt) {
const e = event || window.event;
e.preventDefault();
if (e) {
e.returnValue = '';
}
return '';
}
};
};
// Hook
export default function useExitPrompt(bool) {
const [showExitPrompt, setShowExitPrompt] = useState(bool);
window.onload = function() {
initBeforeUnLoad(showExitPrompt);
};
useEffect(() => {
initBeforeUnLoad(showExitPrompt);
}, [showExitPrompt]);
return [showExitPrompt, setShowExitPrompt];
}
#2组件文件MyComponent.js
注意:showExitPrompt
卸载组件时,必须将状态值重置为默认值。
import useExitPrompt from './useExitPrompt.js'
export default function MyComponent() {
const [showExitPrompt, setShowExitPrompt] = useExitPrompt(false);
const handleClick = (e) => {
e.preventDefault();
setShowExitPrompt(!showExitPrompt)
}
//NOTE: this similar to componentWillUnmount()
useEffect(() => {
return () => {
setShowExitPrompt(false)
}
}, [])
return (
<div className="App">
<form>{/*Your code*/}</form>
<button onClick={handleClick}>Show/Hide the prompt</button>
<Child setShowExitPrompt={setShowExitPrompt} />
</div>
);
}
或者
#2组件文件App.js
通过将其传递给您的子组件,并使用应用程序中任何地方的钩子Context.Provider
访问该值。useContext()
import useExitPrompt from './useExitPrompt.js'
import MyContext from './MyContext.js'
export default function App() {
const [showExitPrompt, setShowExitPrompt] = useExitPrompt(false);
return (
<div className="App">
<MyContext.Provider value={{showExitPrompt, setShowExitPrompt}}>
<MyMainApp />
</MyContext.Provider>
</div>
);
}
export default function MyComponent() {
const { showExitPrompt, setShowExitPrompt } = useContext(MyContext);
//NOTE: this works similar to componentWillUnmount()
useEffect(() => {
return () => {
setShowExitPrompt(false);
}
}, [])
return (
<div>{/* your code */}</div>
);
}
第 2 部分:检测路由/页面更改和浏览器返回
与上述操作类似,当用户点击链接时,他们会被重定向到一个新页面,并且文档及其资源将被卸载。
但是,React Router 的工作方式有所不同,它实现了History API,可以访问浏览器的会话历史记录。点击常规链接,您将进入新的 URL 和新的文档(页面),同时history
允许您在不离开页面的情况下“伪造” URL。
location.pathname
对比history.pushState()
window.location.pathname = '/dummy-page'

垂直/垂直
window.history.pushState({}, '', '/dummy-page')

你看出区别了吗?history.pushState()
只改变了 URL,没有其他变化,整个页面保持不变,同时location.pathname
将您重定向到该新页面,可能会出现 404 错误,因为这样的路由不存在。
getUserConfirmation()
使用和<Prompt/>
组件显示提示
React Router 提供了一个 propgetUserConfirmation()
来<BrowserRouter>
确认导航,以及一个组件<Prompt/>
来显示来自子组件的自定义消息。
#1根文件App.js
import { BrowserRouter } from 'react-router-dom';
function App() {
return (
<BrowserRouter getUserConfirmation={(message, callback) => {
// this is the default behavior
const allowTransition = window.confirm(message);
callback(allowTransition);
}}
>
<Routes />
</BrowserRouter>
);
}
window.confirm()
<Prompt />
将从相应的子组件中显示你传入 React Router 组件的消息。该callback()
函数需要一个布尔参数来阻止跳转到新页面。
#2组件文件MyForm.js
<Prompt />
有 2 个 props,when
和message
。如果when
prop 的值设置为 true,并且用户点击了不同的链接,则会使用message
props 中传递的消息提示用户。
import { Prompt } from 'react-router-dom';
function MyForm() {
const [isFormIncomplete, setIsFormIncomplete] = useState(true);
return (
<div>
<form>{/*Your code*/}</form>
<Prompt
when={isFormIncomplete}
message="Are you sure you want to leave?" />
</div>
)
}
概括
如果用户的操作...
- 删除页面的资源,使用
beforeunload
原始 JavaScript 事件来提示用户。 - 仅更改视图,与组件一起
getUserConfirmation()
使用来提示用户。<BrowserRouter/>
<Prompt />