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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 17:17:51