react-router-dom ^6.3.0中函数组件如何使用<Prompt />做路由拦截?
React Router v6.3.0 实现离开页面确认提示方案
React Router v6 版本移除了v5中内置的<Prompt />组件,要覆盖误触刷新、返回按钮、手动改URL、站内跳转等所有离开场景,需要分别处理浏览器原生离开行为和React Router管控的站内路由跳转两类场景。
1. 处理浏览器原生场景(刷新、关闭标签页、手动输入外站URL跳转)
这类场景由浏览器原生管控,通过监听beforeunload事件实现拦截,封装为自定义Hook即可复用:
import { useEffect } from 'react'; // 传入when参数控制是否开启拦截,message为提示文案 const useBeforeUnloadPrompt = (when = true, message = '当前页面有未保存的内容,确定要离开吗?') => { useEffect(() => { if (!when) return; const handleBeforeUnload = (e) => { e.preventDefault(); // 兼容旧版浏览器,现代浏览器会忽略自定义message,展示系统默认提示 e.returnValue = message; return message; }; window.addEventListener('beforeunload', handleBeforeUnload); return () => window.removeEventListener('beforeunload', handleBeforeUnload); }, [when, message]); };
注意:出于安全规范,Chrome、Edge、Firefox等现代浏览器不会展示自定义的提示文案,会固定展示浏览器默认的离开确认提示,该限制无法通过前端代码绕过。
2. 处理React Router站内跳转场景(返回按钮、站内链接点击、编程式导航)
这类跳转由React Router管控,v6.3.0版本已经内置了拦截能力,只是标记为实验性API,逻辑稳定可直接生产使用:
// 直接从react-router-dom导入,重命名为usePrompt方便后续版本升级替换 import { unstable_usePrompt as useRouterPrompt } from 'react-router-dom';
如果不想使用带unstable_前缀的API,也可以基于路由上下文自己实现拦截Hook:
import { useContext, useEffect, useRef } from 'react'; import { UNSAFE_NavigationContext as NavigationContext } from 'react-router-dom'; const useRouterPrompt = (when = true, message = '当前页面有未保存的内容,确定要离开吗?') => { const { navigator } = useContext(NavigationContext); const unblockRef = useRef(null); useEffect(() => { if (!when) { unblockRef.current?.(); unblockRef.current = null; return; } // 调用history实例的block方法拦截路由跳转 unblockRef.current = navigator.block((transition) => { const isAllowLeave = window.confirm(message); if (isAllowLeave) { // 解除拦截后重试跳转 unblockRef.current?.(); transition.retry(); } }); return () => { unblockRef.current?.(); unblockRef.current = null; }; }, [when, message, navigator]); };
如果需要使用自定义React弹窗组件替代原生window.confirm,只需要把上面window.confirm的逻辑替换为你自己的弹窗状态控制,用户点击确认按钮后再执行unblockRef.current?.()和transition.retry()即可支持异步确认。
3. 实际使用示例
在需要拦截的函数组件中,同时调用两个Hook覆盖所有场景,通过业务状态控制拦截是否生效:
const FormEditPage = () => { // 业务状态:比如表单是否被修改过,初始为false const [hasUnsavedChange, setHasUnsavedChange] = useState(false); // 覆盖刷新/关页场景 useBeforeUnloadPrompt(hasUnsavedChange); // 覆盖站内路由跳转场景 useRouterPrompt(hasUnsavedChange); // 表单提交成功、重置表单时,记得把hasUnsavedChange设为false,避免正常跳转被拦截 const handleSubmit = async () => { await submitForm(); setHasUnsavedChange(false); navigate('/list'); }; return <div>/* 表单内容 */</div> };
注意事项
- 两类拦截必须同时配置,缺任何一个都会存在漏拦截的场景
- 拦截生效的判断条件必须和业务状态严格同步,避免正常操作被拦截
- 不要在
beforeunload的监听逻辑里加异步逻辑,该事件不支持等待异步执行结果
内容的提问来源于stack exchange,提问作者Vishnu Kumar
相关产品推荐
相关产品推荐

