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
相关产品推荐
相关产品推荐

