React Native 避坑指南:Modal没有dismiss,页面跳转回来后,我的 App "卡死"了?
在 React Native 开发中,你是否遇到过这种诡异的现象:
用户在页面 A 打开了一个弹窗(Modal),点击某个按钮跳转到页面 B(比如去设置页)。当用户从页面 B 返回时,发现页面 A 像死机了一样,点什么都没反应,或者界面表现很奇怪。
这并不是玄学,而是 React Navigation 的 Stack 导航机制 与 组件生命周期 共同作用的结果。本文将带你通过一次真实的排查过程,深入理解背后的原理,并总结出一套“防坑心法”。
🛑 案发现场
假设我们有一个“面料接收”的业务场景:
- 用户:在列表页点击某一行,弹出一个详情弹窗 (
ReceptionDetailModal)。 - 用户:点击弹窗里的“打印”按钮。
- App:检测到蓝牙打印机未连接,弹出一个 Alert 提示:“去设置连接打印机”。
- 用户:点击“去设置”,App 跳转到蓝牙设置页 (
PrinterSettingsPage)。 - 用户:连接好打印机,点击左上角返回。
- 结果:回到了列表页,但这时候页面仿佛被冻结了,之前的弹窗还在吗?为什么点不动了?
🔍 核心误区:跳转 ≠ 销毁
很多开发者(尤其是从 Web 开发转过来的)容易产生一个误解:“我跳到新页面了,旧页面不就没了吗?”
错!大错特错!
在 React Navigation 的 Stack(栈)模式下:
- Navigate (前进/入栈) = 盖被子。你拿了一张新纸(新页面)盖在了旧纸(旧页面)上面。旧纸还在桌子上,只是被盖住了。
- GoBack (后退/出栈) = 掀被子。你把最上面那张纸拿走,旧纸原封不动地露了出来。
关键点来了:
当你从页面 A 跳转到页面 B 时,页面 A 并没有被销毁(Unmount),它只是被暂停了。它上面的所有状态、组件实例、监听器,统统都还在!
🕵️♂️ 还原真相:为什么会卡死?
回到我们的案发现场,让我们用“透视眼”看看发生了什么:
- 打开弹窗:
ReceptionDetailModal的状态visible设为true。此时屏幕上覆盖了一层 Modal。 - 跳转去设置:调用
navigation.navigate('Settings')。- 此时,设置页(Page B)被推入栈顶,覆盖了接收页(Page A)。
- 但是! Page A 的 Modal 并没有关闭。它的
visible依然是true。它只是被 Page B 挡住了,你看不到它而已。
- 从设置页返回:调用
navigation.goBack()。- Page B 被移除。
- Page A 重见天日。
- 尴尬时刻:因为刚才没关 Modal,它现在依然顽固地显示在那里。
造成“卡死”错觉的原因:
- 遮罩层拦截:很多 Modal 组件(特别是自定义的 BottomModal)都有一个半透明的背景遮罩(Backdrop)。这个遮罩层是全屏的,用来拦截点击事件(点击空白处关闭)。当你回来时,这个透明的遮罩层依然挡在最前面,你点击列表或其他按钮,其实都点在了遮罩层上,所以看起来像“点不动”或“卡死”。
- 焦点/状态错乱:有些依赖原生实现的 Modal,在页面被覆盖又恢复的过程中,可能会丢失焦点,或者动画状态卡在中间,导致无法正常关闭。
🛠️ 解决方案:出门前先关灯
解决这个问题的核心原则非常简单:清理现场。
在离开当前页面上下文(Context)之前,必须把所有临时的、模态的(Modal)、阻塞性的状态清理干净。
代码修复示例
错误写法 ❌
const goToPrinterSettings = () => {
// 直接跳转,Modal 还开着!
navigation.navigate('PrinterSettingsPage');
};
正确写法 ✅
const goToPrinterSettings = () => {
// 1. 先把弹窗关掉!
setVisible(false);
// 2. 然后再跳转
navigation.navigate('PrinterSettingsPage');
};
💡 进阶心法:排查三问
下次遇到类似的“跳转回来后出 bug”的问题,请在脑海中灵魂三问:
1. 出门前打扫了吗?
跳转(navigate)之前,有没有打开的 Modal、Dialog、ActionSheet 或者 Loading菊花图?
原则:如果有,一定要先显式关闭 (
setVisible(false)) 再跳转。
2. 还有回来的必要吗?
如果这是一个“提交后跳转”的操作(比如:保存 -> 跳转列表),失败了是否需要留在当前页重试?
原则:不要无脑在
finally里写goBack()。如果次要流程(如打印)失败,应该停留在当前页,给用户重试的机会,而不是强行返回导致数据丢失。
3. 我在跟谁说话?
如果在异步回调(比如 setTimeout 或 API 请求回调)里操作 UI,要小心页面是否还“活着”。
原则:虽然 Stack 导航会保留页面,但如果是
replace或reset操作,页面是真的会被销毁的。这时候再 setState 会导致内存泄漏或报错。
📝 总结
React Native 的导航栈就像叠盘子。你以为盘子不见了,其实它只是被压在了下面。
- Modal 不会自动消失:除非你关掉它,或者父页面被销毁。
- 跳转不是销毁:只是视觉上的遮挡。
- 手动管理状态:在该关闭的时候手动关闭,是开发者的责任。
希望这篇避坑指南能帮你少掉几根头发!🚀