如何在react-router-dom v6中捕获pop事件,实现未保存页面导航拦截?
解决react-router-dom v6中未保存更改时的pop事件拦截问题
你遇到的核心问题是:浏览器的前进/后退(pop事件)属于原生浏览器事件,并非通过react-router的navigator.push或navigator.go触发,所以重写这两个方法无法拦截这类导航行为。下面提供两种可行的解决方案:
方案一:使用官方推荐的useBlocker(v6.4+)
react-router-dom v6.4及以上版本提供了useBlocker API,专门用于拦截所有导航行为(包括push、pop、go),无需依赖UNSAFE的上下文,是官方推荐的实现方式。
import { useBlocker, useEffect } from 'react-router-dom'; const useConfirmExit = (confirmExit: () => Promise<boolean>, when = true, callbackfn?: (arg: any) => void) => { // 创建拦截器,返回false时阻止导航 const blocker = useBlocker(async () => { if (!when) return true; return await confirmExit(); }); useEffect(() => { // 当导航被拦截时,处理用户确认逻辑 if (blocker.state === 'blocked') { const handleConfirm = async () => { const canExit = await confirmExit(); if (canExit) { blocker.proceed(); // 允许导航 callbackfn?.('pop' || blocker.navigation.location); } else { blocker.reset(); // 取消导航,恢复状态 } }; handleConfirm(); } }, [blocker, confirmExit, when, callbackfn]); };
方案二:兼容UNSAFE上下文+监听popstate事件
如果你需要继续使用UNSAFE_NavigationContext,可以结合浏览器原生的popstate事件来处理后退/前进导航,同时避免竞态条件:
import { useEffect, useContext, useRef } from 'react'; import { UNSAFE_NavigationContext as NavigationContext, useLocation } from 'react-router-dom'; const useConfirmExit = function (confirmExit: () => Promise<boolean>, when = true, callbackfn: (arg0: any) => void): void { const { navigator } = useContext(NavigationContext); const location = useLocation(); const lastLocation = useRef(location); const isNavigating = useRef(false); // 标记是否正在处理导航,避免重复触发 useEffect(() => { if (!when) return () => {}; // 拦截push和go方法 const originalPush = navigator.push; const originalGo = navigator.go; navigator.push = async (...args) => { if (isNavigating.current) return; isNavigating.current = true; try { const result = await confirmExit(); if (result !== false) { callbackfn(args); originalPush(...args); } } finally { isNavigating.current = false; } }; navigator.go = async (delta) => { if (isNavigating.current) return; isNavigating.current = true; try { const result = await confirmExit(); if (result !== false) { originalGo(delta); } } finally { isNavigating.current = false; } }; // 监听浏览器popstate事件(后退/前进) const handlePopState = async (event) => { if (!when || isNavigating.current) return; isNavigating.current = true; try { const result = await confirmExit(); if (result === false) { // 用户取消离开,恢复到之前的路由 window.history.pushState( lastLocation.current.state, '', lastLocation.current.pathname + lastLocation.current.search ); } else { callbackfn(event); lastLocation.current = location; } } finally { isNavigating.current = false; } }; window.addEventListener('popstate', handlePopState); // 监听路由变化,更新记录的最后位置 const unlisten = navigator.listen(() => { lastLocation.current = location; }); // 组件卸载时恢复原始方法并移除监听 return () => { navigator.push = originalPush; navigator.go = originalGo; window.removeEventListener('popstate', handlePopState); unlisten(); }; }, [navigator, confirmExit, when, location, callbackfn]); };
注意事项
UNSAFE_NavigationContext带有UNSAFE标记,未来版本可能被废弃,优先选择方案一- 异步确认逻辑中要使用状态标记(如
isNavigating),避免重复触发拦截导致的异常
内容的提问来源于stack exchange,提问作者Ankita Goyal
相关产品推荐
相关产品推荐

