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

React Native-Web如何阻止用户离开含未提交修改表单的组件?

脏表单全场景跳转拦截实现方案

核心思路

原生提供的beforeRemove事件仅在组件卸载时触发,无法覆盖组件不卸载的栈内/跨栈跳转、快捷链接点击等场景,需要同时实现全局导航动作拦截 + 组件blur事件兜底 + 原有beforeRemove兼容三层逻辑,才能覆盖所有跳转场景。

具体实现步骤

  • 全局绑定导航拦截回调:React Navigation 6及以上版本支持NavigationContainer的onBeforeStateChange属性,所有跳转动作(包括栈内、跨栈、快捷链接触发的跳转)都会先进入这个回调,在回调内统一判断当前活跃页面是否为脏表单,是则先拦截跳转,暂存跳转动作并弹出确认框。
  • 表单页维护脏状态:在表单组件内用状态变量标记表单是否已修改,提交成功后重置标记,同时将状态同步到全局(可用全局状态管理库或全局变量存储,多表单场景可存路由名-脏状态的映射表)。
  • 绑定blur事件兜底:给表单页的navigation实例绑定blur事件监听器,触发时如果是脏表单,先调用navigation.navigate切回当前页中断跳转,再弹出确认框。
  • 保留beforeRemove事件监听:兼容物理返回、手势返回、当前栈pop到首页等会卸载组件的场景,复用同一套确认弹窗逻辑。

关键代码示例

全局导航容器配置

import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native';
import { useState } from 'react';
// 自定义的全局确认弹窗组件
import ConfirmModal from './components/ConfirmModal';

export default function App() {
  const navigationRef = useNavigationContainerRef();
  // 存储被拦截的待执行跳转动作
  const [pendingAction, setPendingAction] = useState(null);
  const [isConfirmVisible, setIsConfirmVisible] = useState(false);

  const handleBeforeStateChange = (action) => {
    const currentRoute = navigationRef.getCurrentRoute();
    // 判断当前活跃路由是否为脏表单页
    if (currentRoute?.name === 'FormPage' && global.formDirtyState === true) {
      setPendingAction(action);
      setIsConfirmVisible(true);
      // 返回false阻止默认跳转
      return false;
    }
    return true;
  };

  // 用户确认离开回调
  const handleConfirmLeave = () => {
    setIsConfirmVisible(false);
    global.formDirtyState = false;
    // 执行之前被拦截的跳转动作
    if (pendingAction) {
      navigationRef.dispatch(pendingAction);
      setPendingAction(null);
    }
  };

  // 用户取消离开回调
  const handleCancelLeave = () => {
    setIsConfirmVisible(false);
    setPendingAction(null);
  };

  return (
    <>
      <NavigationContainer
        ref={navigationRef}
        onBeforeStateChange={handleBeforeStateChange}
      >
        {/* 你的项目路由配置 */}
      </NavigationContainer>
      <ConfirmModal
        visible={isConfirmVisible}
        onConfirm={handleConfirmLeave}
        onCancel={handleCancelLeave}
        content="表单内容已修改,确定要离开当前页面吗?"
      />
    </>
  )
}

表单页逻辑

import { useNavigation } from '@react-navigation/native';
import { useState, useEffect } from 'react';

export default function FormPage() {
  const [isFormDirty, setIsFormDirty] = useState(false);
  const navigation = useNavigation();

  // 表单内容变更时触发,标记为脏表单
  const handleFormInputChange = () => {
    setIsFormDirty(true);
  };

  // 表单提交成功时触发,清除脏标记
  const handleFormSubmitSuccess = () => {
    setIsFormDirty(false);
  };

  useEffect(() => {
    // 同步脏状态到全局,供导航拦截器判断
    global.formDirtyState = isFormDirty;

    // 绑定blur事件兜底处理
    const unsubscribeBlur = navigation.addListener('blur', () => {
      if (isFormDirty) {
        // 切回当前页面中断跳转
        navigation.navigate({ name: 'FormPage', merge: true });
        // 触发全局确认弹窗
        setIsConfirmVisible(true);
      }
    });

    // 保留beforeRemove事件兼容组件卸载场景
    const unsubscribeBeforeRemove = navigation.addListener('beforeRemove', (e) => {
      if (!isFormDirty) return;
      e.preventDefault();
      setIsConfirmVisible(true);
    });

    return () => {
      unsubscribeBlur();
      unsubscribeBeforeRemove();
      // 页面卸载时清空全局脏状态
      global.formDirtyState = false;
    };
  }, [navigation, isFormDirty]);

  // 表单渲染逻辑
  return <Form onChange={handleFormInputChange} onSubmitSuccess={handleFormSubmitSuccess} />
}

注意事项

  • 多表单场景下不要用单一全局变量存脏状态,可改用Zustand/Redux等全局状态库存储Map<路由名, 脏状态>的结构,避免多个表单互相干扰。
  • React Navigation 5版本没有onBeforeStateChange属性,可通过重写navigation的navigate/push/replace等方法实现跳转拦截,逻辑和全局回调一致。
  • 用户确认离开后要先重置对应表单的脏状态,再执行暂存的跳转动作,避免重复触发拦截。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 05:30:04