You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 21:46:29