React Router DOM v6如何阻止导航?求v5 history.block()适配方案
React Router v6 表单未保存阻止导航适配方案
React Router v6 移除了 history.block() 方法,以下是适配你原有v5逻辑的两种解决方案:
方案1:使用官方useBlocker钩子(推荐,v6.4+ 数据路由器)
如果你的项目已迁移到数据路由器(通过createBrowserRouter创建),直接使用useBlocker钩子实现拦截:
import { useBlocker, useLocation, useNavigate } from 'react-router-dom'; import { useEffect, useState } from 'react'; function YourFormComponent() { const [show, setShow] = useState(false); // 表单是否有未保存内容 const [showPrompt, setShowPrompt] = useState(false); const [currentPath, setCurrentPath] = useState(''); const ALLOWED_PATHS = ['/allowed-path-1', '/allowed-path-2']; // 允许无提示跳转的路径 const location = useLocation(); const navigate = useNavigate(); // 配置导航拦截规则 const blocker = useBlocker((tx) => { // 表单无未保存内容时不拦截 if (!show) return false; // 忽略浏览器回退操作(对应v5的action !== 'POP') if (tx.navigationType === 'POP') return false; // 忽略当前页面路由和允许的路径 const targetPath = tx.location.pathname; if (targetPath.includes(location.pathname) || ALLOWED_PATHS.includes(targetPath)) { return false; } // 记录目标路径并弹出提示模态框 setCurrentPath(targetPath); setShowPrompt(true); // 返回true拦截当前导航 return true; }); // 根据表单状态启用/禁用拦截 useEffect(() => { blocker.enable(show); }, [blocker, show]); // 确认跳转:继续之前被拦截的导航 const handleConfirmNavigation = () => { setShowPrompt(false); blocker.proceed(); }; // 取消跳转:中止导航 const handleCancelNavigation = () => { setShowPrompt(false); blocker.reset(); }; // ... 渲染表单和提示模态框的逻辑 }
方案2:适配传统<BrowserRouter>
如果仍在使用传统的<BrowserRouter>,通过自定义导航拦截和beforeunload事件实现:
import { useLocation, useNavigate } from 'react-router-dom'; import { useEffect, useState } from 'react'; function YourFormComponent() { const [show, setShow] = useState(false); // 表单是否有未保存内容 const [showPrompt, setShowPrompt] = useState(false); const [pendingPath, setPendingPath] = useState(''); const ALLOWED_PATHS = ['/allowed-path-1', '/allowed-path-2']; const location = useLocation(); const navigate = useNavigate(); const [originalNavigate, setOriginalNavigate] = useState(navigate); // 拦截内部路由跳转 useEffect(() => { const customNavigate = (to, options) => { if (!show) { return originalNavigate(to, options); } const targetPath = typeof to === 'string' ? to : to.pathname; // 忽略当前页面和允许的路径 if (targetPath.includes(location.pathname) || ALLOWED_PATHS.includes(targetPath)) { return originalNavigate(to, options); } // 记录路径并弹出提示 setPendingPath(targetPath); setShowPrompt(true); return false; }; // 替换默认的navigate方法 setOriginalNavigate(navigate); navigate = customNavigate; return () => { // 组件卸载时恢复原navigate方法 navigate = originalNavigate; }; }, [navigate, show, location.pathname, ALLOWED_PATHS]); // 拦截页面刷新/关闭 useEffect(() => { const handleBeforeUnload = (e) => { if (show) { e.preventDefault(); e.returnValue = ''; // 兼容部分浏览器的提示文案要求 } }; window.addEventListener('beforeunload', handleBeforeUnload); return () => { window.removeEventListener('beforeunload', handleBeforeUnload); }; }, [show]); // 确认跳转 const handleConfirm = () => { setShowPrompt(false); originalNavigate(pendingPath); }; // 取消跳转 const handleCancel = () => { setShowPrompt(false); setPendingPath(''); }; // ... 渲染表单和提示模态框的逻辑 }
核心差异说明
- v5的
history.block()替换为v6的useBlocker(数据路由器)或自定义导航拦截逻辑 - 导航类型判断从
action !== 'POP'改为tx.navigationType !== 'POP'(数据路由器场景) - 无需手动清理拦截器,
useBlocker会在组件卸载时自动清理拦截规则
内容的提问来源于stack exchange,提问作者Kesavan R
相关产品推荐
相关产品推荐

