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

react-router-dom BrowserRouter自定义未保存数据警告及路由拦截咨询

实现Redux应用中路由切换时的未保存表单拦截

我来帮你搞定这个路由拦截的问题!在Redux应用里监听URL变化,拦截未保存表单的路由跳转,其实有两种常用的方案,我都给你捋清楚:

一、React Router v6+:用useBlocker/usePrompt钩子(推荐)

从React Router v6开始,官方提供了专门的钩子来处理路由跳转拦截,完全可以组件级控制,还能无缝结合Redux的表单状态,非常灵活。

第一步:标记表单是否有未修改内容

先在你的表单组件里,从Redux拿表单的初始值和当前值,判断是否有未保存的修改:

import { useSelector } from 'react-redux';

const MyForm = () => {
  // 从Redux获取表单初始值和当前输入值
  const initialFormData = useSelector(state => state.form.initialData);
  const currentFormData = useSelector(state => state.form.currentData);
  // 简单对比判断是否有未保存修改(复杂表单可以用更精细的对比逻辑)
  const hasUnsavedChanges = JSON.stringify(initialFormData) !== JSON.stringify(currentFormData);
  
  // ...你的表单渲染和输入逻辑
}

第二步:用useBlocker拦截路由跳转

useBlocker会在路由要跳转时触发,你可以在里面弹出自定义弹窗,然后决定是否允许跳转:

import { useBlocker, useNavigate } from 'react-router-dom';
import { useState } from 'react';

const MyForm = () => {
  // ...上面的hasUnsavedChanges逻辑
  const navigate = useNavigate();
  const [showConfirmModal, setShowConfirmModal] = useState(false);
  const [pendingRoute, setPendingRoute] = useState(null);

  // 定义拦截逻辑
  const blocker = useBlocker(({ currentLocation, nextLocation }) => {
    // 只有当有未保存修改,且不是同一个路由跳转时才拦截
    if (hasUnsavedChanges && currentLocation.pathname !== nextLocation.pathname) {
      setPendingRoute(nextLocation);
      setShowConfirmModal(true);
      return false; // 先阻止跳转
    }
    return true; // 允许正常跳转
  });

  // 用户确认离开的处理
  const handleConfirmLeave = () => {
    setShowConfirmModal(false);
    if (pendingRoute) {
      navigate(pendingRoute.pathname);
      blocker.reset(); // 重置拦截器,避免后续干扰
    }
  };

  // 用户取消离开的处理
  const handleCancelLeave = () => {
    setShowConfirmModal(false);
    setPendingRoute(null);
    blocker.reset();
  };

  return (
    <>
      {/* 你的表单内容 */}
      {/* 自定义确认弹窗 */}
      {showConfirmModal && (
        <div className="custom-modal">
          <p>你有未保存的修改,确定要离开吗?</p>
          <button onClick={handleConfirmLeave}>确定离开</button>
          <button onClick={handleCancelLeave}>取消</button>
        </div>
      )}
    </>
  );
};

这种方式完全不需要全局配置,弹窗样式想怎么改就怎么改,非常适合你的需求。

二、适配getUserConfirmation全局配置方案

如果你必须用getUserConfirmation,且你的代码要求它无参数、返回void,那可以结合Redux的全局状态来实现:

第一步:在Redux里维护未保存状态

先给Redux加个状态,用来标记是否有未保存的表单修改:

// action type
export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES';

// action creator
export const setUnsavedChanges = (hasChanges) => ({
  type: SET_UNSAVED_CHANGES,
  payload: hasChanges
});

// reducer
const initialUIState = {
  hasUnsavedChanges: false
};

export const uiReducer = (state = initialUIState, action) => {
  switch (action.type) {
    case SET_UNSAVED_CHANGES:
      return { ...state, hasUnsavedChanges: action.payload };
    default:
      return state;
  }
};

记得在表单内容变化时,调用setUnsavedChanges更新这个状态。

第二步:自定义getUserConfirmation

在配置Router的时候,传入你自定义的getUserConfirmation。官方的这个方法会接收message和callback,但你可以忽略message,只利用callback来控制是否允许跳转:

import { BrowserRouter as Router } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import { useState } from 'react';

const CustomRouter = ({ children }) => {
  const hasUnsavedChanges = useSelector(state => state.ui.hasUnsavedChanges);
  const [showModal, setShowModal] = useState(false);
  const [resolveCallback, setResolveCallback] = useState(null);

  // 自定义getUserConfirmation,忽略第一个message参数
  const getUserConfirmation = (_, callback) => {
    // 没有未保存修改,直接允许跳转
    if (!hasUnsavedChanges) {
      callback(true);
      return;
    }
    // 有未保存修改,弹出自定义弹窗
    setShowModal(true);
    setResolveCallback(callback);
  };

  const handleConfirm = () => {
    setShowModal(false);
    resolveCallback(true); // 告诉Router允许跳转
  };

  const handleCancel = () => {
    setShowModal(false);
    resolveCallback(false); // 告诉Router阻止跳转
  };

  return (
    <>
      <Router getUserConfirmation={getUserConfirmation}>
        {children}
      </Router>
      {/* 自定义确认弹窗 */}
      {showModal && (
        <div className="custom-modal">
          <p>你有未保存的修改,确定要离开吗?</p>
          <button onClick={handleConfirm}>确定离开</button>
          <button onClick={handleCancel}>取消</button>
        </div>
      )}
    </>
  );
};

之后用这个CustomRouter包裹你的整个应用就行。

额外说明

  • 如果你用的是React Router v5或更早版本,官方没有useBlocker,可以用Prompt组件,不过它默认是浏览器弹窗,要自定义的话还是得结合getUserConfirmation:
    import { Prompt } from 'react-router-dom';
    
    const MyForm = () => {
      const hasUnsavedChanges = useSelector(state => state.ui.hasUnsavedChanges);
      return (
        <>
          <Prompt
            when={hasUnsavedChanges}
            message="你有未保存的修改,确定要离开吗?"
          />
          {/* 表单内容 */}
        </>
      );
    };
    
  • 不管用哪种方案,都要确保表单每次修改时,及时更新Redux里的hasUnsavedChanges状态,不然拦截逻辑会失效。

内容的提问来源于stack exchange,提问作者vbullinger

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 10:06:23