React Native 避坑指南:Modal没有dismiss,页面跳转回来后,我的 App "卡死"了?

7 阅读4分钟

React Native 避坑指南:Modal没有dismiss,页面跳转回来后,我的 App "卡死"了?

在 React Native 开发中,你是否遇到过这种诡异的现象:

用户在页面 A 打开了一个弹窗(Modal),点击某个按钮跳转到页面 B(比如去设置页)。当用户从页面 B 返回时,发现页面 A 像死机了一样,点什么都没反应,或者界面表现很奇怪。

这并不是玄学,而是 React Navigation 的 Stack 导航机制组件生命周期 共同作用的结果。本文将带你通过一次真实的排查过程,深入理解背后的原理,并总结出一套“防坑心法”。


🛑 案发现场

假设我们有一个“面料接收”的业务场景:

  1. 用户:在列表页点击某一行,弹出一个详情弹窗 (ReceptionDetailModal)。
  2. 用户:点击弹窗里的“打印”按钮。
  3. App:检测到蓝牙打印机未连接,弹出一个 Alert 提示:“去设置连接打印机”。
  4. 用户:点击“去设置”,App 跳转到蓝牙设置页 (PrinterSettingsPage)。
  5. 用户:连接好打印机,点击左上角返回。
  6. 结果:回到了列表页,但这时候页面仿佛被冻结了,之前的弹窗还在吗?为什么点不动了?

🔍 核心误区:跳转 ≠ 销毁

很多开发者(尤其是从 Web 开发转过来的)容易产生一个误解:“我跳到新页面了,旧页面不就没了吗?”

错!大错特错!

在 React Navigation 的 Stack(栈)模式下:

  • Navigate (前进/入栈) = 盖被子。你拿了一张新纸(新页面)盖在了旧纸(旧页面)上面。旧纸还在桌子上,只是被盖住了。
  • GoBack (后退/出栈) = 掀被子。你把最上面那张纸拿走,旧纸原封不动地露了出来。

关键点来了:

当你从页面 A 跳转到页面 B 时,页面 A 并没有被销毁(Unmount),它只是被暂停了。它上面的所有状态、组件实例、监听器,统统都还在!


🕵️‍♂️ 还原真相:为什么会卡死?

回到我们的案发现场,让我们用“透视眼”看看发生了什么:

  1. 打开弹窗ReceptionDetailModal 的状态 visible 设为 true。此时屏幕上覆盖了一层 Modal。
  2. 跳转去设置:调用 navigation.navigate('Settings')
    • 此时,设置页(Page B)被推入栈顶,覆盖了接收页(Page A)。
    • 但是! Page A 的 Modal 并没有关闭。它的 visible 依然是 true。它只是被 Page B 挡住了,你看不到它而已。
  3. 从设置页返回:调用 navigation.goBack()
    • Page B 被移除。
    • Page A 重见天日。
    • 尴尬时刻:因为刚才没关 Modal,它现在依然顽固地显示在那里。

造成“卡死”错觉的原因:

  1. 遮罩层拦截:很多 Modal 组件(特别是自定义的 BottomModal)都有一个半透明的背景遮罩(Backdrop)。这个遮罩层是全屏的,用来拦截点击事件(点击空白处关闭)。当你回来时,这个透明的遮罩层依然挡在最前面,你点击列表或其他按钮,其实都点在了遮罩层上,所以看起来像“点不动”或“卡死”。
  2. 焦点/状态错乱:有些依赖原生实现的 Modal,在页面被覆盖又恢复的过程中,可能会丢失焦点,或者动画状态卡在中间,导致无法正常关闭。

🛠️ 解决方案:出门前先关灯

解决这个问题的核心原则非常简单:清理现场

在离开当前页面上下文(Context)之前,必须把所有临时的、模态的(Modal)、阻塞性的状态清理干净。

代码修复示例

错误写法 ❌

const goToPrinterSettings = () => {
  // 直接跳转,Modal 还开着!
  navigation.navigate('PrinterSettingsPage'); 
};

正确写法 ✅

const goToPrinterSettings = () => {
  // 1. 先把弹窗关掉!
  setVisible(false); 
  
  // 2. 然后再跳转
  navigation.navigate('PrinterSettingsPage'); 
};

💡 进阶心法:排查三问

下次遇到类似的“跳转回来后出 bug”的问题,请在脑海中灵魂三问:

1. 出门前打扫了吗?

跳转(navigate)之前,有没有打开的 ModalDialogActionSheet 或者 Loading菊花图

原则:如果有,一定要先显式关闭 (setVisible(false)) 再跳转。

2. 还有回来的必要吗?

如果这是一个“提交后跳转”的操作(比如:保存 -> 跳转列表),失败了是否需要留在当前页重试?

原则:不要无脑在 finally 里写 goBack()。如果次要流程(如打印)失败,应该停留在当前页,给用户重试的机会,而不是强行返回导致数据丢失。

3. 我在跟谁说话?

如果在异步回调(比如 setTimeout 或 API 请求回调)里操作 UI,要小心页面是否还“活着”。

原则:虽然 Stack 导航会保留页面,但如果是 replacereset 操作,页面是真的会被销毁的。这时候再 setState 会导致内存泄漏或报错。


📝 总结

React Native 的导航栈就像叠盘子。你以为盘子不见了,其实它只是被压在了下面。

  • Modal 不会自动消失:除非你关掉它,或者父页面被销毁。
  • 跳转不是销毁:只是视觉上的遮挡。
  • 手动管理状态:在该关闭的时候手动关闭,是开发者的责任。

希望这篇避坑指南能帮你少掉几根头发!🚀