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

React实现表单未保存离开确认提示(不使用react-router-dom Prompt)

未保存更改离开拦截实现方案(无需依赖react-router-dom的<Prompt/>组件)

完全可以通过原生事件监听+本地状态控制的方式实现需求,覆盖「关闭模态对话框」「跳转/关闭页面」两类拦截场景,所有交互逻辑完全可控,也能匹配自定义的弹窗样式。

核心前置状态

首先维护一个isFormDirty布尔状态,标记表单是否存在未保存的修改:

  • 表单初始值加载完成后,缓存一份初始值的深拷贝快照
  • 监听表单所有输入项的变化,每次值更新时对比当前值和初始快照,只要存在字段差异就将isFormDirty设为true
  • 表单提交成功、用户主动重置表单时,同步更新初始值快照,将isFormDirty重置为false

模态框关闭拦截实现

不要直接使用模态框组件默认的关闭逻辑,所有关闭触发源(关闭按钮点击、遮罩层点击、ESC键按下)统一绑定自定义处理函数:

  • 函数首先判断isFormDirty状态:如果表单无未保存更改,直接执行模态框关闭、表单重置逻辑
  • 如果存在未保存更改,唤起自定义的确认提示弹窗,不执行默认关闭
  • 确认弹窗点击取消:直接关闭确认弹窗,终止模态框关闭流程,保留用户已输入内容
  • 确认弹窗点击确定:关闭确认弹窗,重置isFormDirty状态,再执行模态框关闭、表单重置逻辑
    参考代码片段:
const handleDialogAttemptClose = () => {
  if (!isFormDirty) {
    setDialogOpen(false);
    resetFormToInitial();
    return;
  }
  setConfirmModalOpen(true);
};

// 确认弹窗操作逻辑
const handleConfirmLeave = () => {
  setConfirmModalOpen(false);
  setIsFormDirty(false);
  setDialogOpen(false);
  resetFormToInitial();
};

const handleStayOnForm = () => {
  setConfirmModalOpen(false);
};

页面跳转/关闭拦截实现

针对整页跳转、刷新、关闭浏览器标签页的场景,直接使用浏览器原生的beforeunload事件实现拦截:

  • 在表单组件挂载时注册beforeunload事件监听,组件卸载时移除监听避免内存泄漏
  • 事件回调中判断isFormDirty状态,如果存在未保存更改,阻止默认事件并给returnValue赋值提示文案,浏览器会自动弹出原生离开确认框
  • 如果是单页应用内部的路由跳转,只需要在你使用的路由库的跳转前置钩子中加入相同的脏状态判断即可,不需要依赖路由库内置的Prompt组件
    参考代码片段:
useEffect(() => {
  const handlePageUnload = (e) => {
    if (isFormDirty) {
      e.preventDefault();
      e.returnValue = '您确定要离开吗?当前存在未保存的更改';
      return e.returnValue;
    }
  };
  window.addEventListener('beforeunload', handlePageUnload);
  return () => window.removeEventListener('beforeunload', handlePageUnload);
}, [isFormDirty]);

注意:出于安全限制,浏览器对beforeunload事件的自定义弹窗样式有强制要求,无法替换为你自己实现的UI弹窗,仅模态框关闭、单页内部路由跳转场景可以使用完全自定义样式的确认提示。

你示例代码中的<PromptIfDirty/>组件就可以按照上述逻辑自行封装,不需要引入额外依赖,所有拦截时机、交互逻辑都可以按需调整。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 02:36:14