
网罗开发 (小红书、快手、视频号同名)
大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验 。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索"展菲",即可纵览我在各大平台的知识足迹。
📣 公众号"Swift社区",每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友"fzhanfei",与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
-
- 摘要
- 一个非常常见但危险的认知误区
- [页面存在 ≠ 页面可见,这句话到底什么意思?](#页面存在 ≠ 页面可见,这句话到底什么意思?)
- [RN 页面真实生命周期拆解](#RN 页面真实生命周期拆解)
- [隐藏 Bug 场景一:useEffect 在页面不可见时仍然执行](#隐藏 Bug 场景一:useEffect 在页面不可见时仍然执行)
- [focus / blur 和 mount / unmount 的本质区别](#focus / blur 和 mount / unmount 的本质区别)
-
- [mount / unmount 关注的是"组件是否存在"](#mount / unmount 关注的是“组件是否存在”)
- [focus / blur 关注的是"页面是否当前活跃"](#focus / blur 关注的是“页面是否当前活跃”)
- [可运行 Demo:不可见页面触发副作用](#可运行 Demo:不可见页面触发副作用)
- [正确做法:用 focus 来管理副作用](#正确做法:用 focus 来管理副作用)
- [隐藏 Bug 场景二:页面状态的"幽灵更新"](#隐藏 Bug 场景二:页面状态的“幽灵更新”)
- 实战建议:副作用管理的几条铁律
-
- [1. 只要和"页面可见性"有关,一定不要只用 useEffect](#1. 只要和“页面可见性”有关,一定不要只用 useEffect)
- [2. 网络请求也要区分"是否仍然关心结果"](#2. 网络请求也要区分“是否仍然关心结果”)
- [3. 不要指望"页面切走了就会 unmount"](#3. 不要指望“页面切走了就会 unmount”)
- 总结
摘要
在 React Native 项目里,很多开发者都会默认认为:只要页面不在当前屏幕上,就等同于"不存在" 。
但现实往往刚好相反------页面可能已经不可见了,但它还活得好好的。
这类误判,通常会带来一些非常隐蔽、非常难排查的问题,比如:
- 页面切走了,
useEffect里的逻辑还在跑 - 网络请求、定时器、轮询在后台偷偷执行
- 状态被更新了,但 UI 不可见,回到页面直接"状态错乱"
- 某些 Bug 只有多次跳转页面后才出现
这篇文章我们就专门聊清楚一件事:
在 RN 中,"页面存在"和"页面可见"到底有什么区别?以及这些区别会带来哪些坑。
一个非常常见但危险的认知误区
很多 RN 项目一开始,页面结构都很简单:
txt
A 页面 → B 页面 → 返回 A 页面
直觉上你可能会认为:
- 跳到 B 页面时,A 页面已经"销毁"
- 所有副作用自然就停了
但在真实的 RN Navigation 里,默认并不是这样。
大多数导航库(尤其是 react-navigation)默认行为是:
- 页面被压入栈中
- 页面组件仍然处于 mounted 状态
- 只是"不可见",而不是 unmount
这就埋下了隐患。
页面存在 ≠ 页面可见,这句话到底什么意思?
我们先用一句话把概念说清楚:
页面存在(mounted) :组件还在内存里,状态还在,副作用也可能在跑
页面可见(focused):页面当前是否处于用户正在看的那个状态
这两者在 RN 里是两个完全不同的维度。
一个简单对照表
| 状态 | 页面是否在内存 | useEffect 是否存在 | 用户是否可见 |
|---|---|---|---|
| mount + focus | 是 | 是 | 是 |
| mount + blur | 是 | 是 | 否 |
| unmount | 否 | 否 | 否 |
最容易出问题的就是:mount + blur 这一档。
RN 页面真实生命周期拆解
我们结合 react-navigation 来看一个典型页面的生命周期。
常见生命周期触发顺序
-
页面第一次进入
- 组件
mount useEffect(() => {}, [])执行- 页面
focus
- 组件
-
跳转到下一个页面
- 当前页面
blur - 组件仍然 mounted
- 当前页面
-
返回当前页面
- 页面重新
focus - 不会重新 mount
- 页面重新
如果你只在 useEffect 里写逻辑,很可能会忽略第 2、3 步。
隐藏 Bug 场景一:useEffect 在页面不可见时仍然执行
先看一个非常常见的写法。
问题示例代码
tsx
useEffect(() => {
const timer = setInterval(() => {
console.log('polling...');
}, 3000);
return () => {
clearInterval(timer);
};
}, []);
这个代码乍一看没问题:
- 有定时器
- 有清理逻辑
但问题在于:
只有在组件 unmount 时,清理函数才会执行
页面只是 blur 时,并不会触发 cleanup
实际效果
- 页面 A 启动定时器
- 跳转到页面 B
- 页面 A 的定时器 仍在后台执行
- 多次进出页面,定时器可能叠加
这在真实项目中,非常容易导致:
- CPU 占用升高
- 网络请求数量异常
- 页面切换越多越卡
focus / blur 和 mount / unmount 的本质区别
这是理解整个问题的关键。
mount / unmount 关注的是"组件是否存在"
- React 层级概念
- 决定 state、effect 是否存在
focus / blur 关注的是"页面是否当前活跃"
- Navigation 层级概念
- 决定是否应该执行业务逻辑
业务副作用(请求、定时器、订阅)往往更关心 focus,而不是 mount。
可运行 Demo:不可见页面触发副作用
下面这个 Demo 可以非常直观地感受到问题。
示例页面结构
- HomeScreen
- DetailScreen
从 Home 跳到 Detail,再返回 Home。
错误示例:只依赖 useEffect
tsx
function HomeScreen() {
useEffect(() => {
const timer = setInterval(() => {
console.log('Home polling...');
}, 2000);
return () => {
console.log('Home cleanup');
clearInterval(timer);
};
}, []);
return (
<View>
<Text>Home</Text>
</View>
);
}
运行现象
- 进入 Home:开始打印
- 跳到 Detail:仍然在打印
- 多次来回:日志越来越多
正确做法:用 focus 来管理副作用
react-navigation 提供了非常关键的 API:useFocusEffect。
改造后的示例代码
tsx
import { useFocusEffect } from '@react-navigation/native';
import { useCallback } from 'react';
function HomeScreen() {
useFocusEffect(
useCallback(() => {
const timer = setInterval(() => {
console.log('Home polling...');
}, 2000);
return () => {
console.log('Home blur cleanup');
clearInterval(timer);
};
}, [])
);
return (
<View>
<Text>Home</Text>
</View>
);
}
这个写法的核心优势
- 页面 focus 时启动副作用
- 页面 blur 时立即清理
- 不依赖组件是否 unmount
这才是真正符合"页面是否可见"的业务语义。
隐藏 Bug 场景二:页面状态的"幽灵更新"
还有一种非常隐蔽的问题,比定时器更难发现。
场景描述
- 页面 A 发起请求
- 用户立刻跳到页面 B
- 请求返回后,更新了页面 A 的 state
- 用户再返回 A,发现 UI 状态"突然变了"
问题示例
tsx
useEffect(() => {
fetchData().then(res => {
setData(res);
});
}, []);
如果这个请求发生在页面 blur 期间:
- UI 不可见
- 状态却被更新了
- 用户完全不知道发生了什么
这类问题通常表现为:
- 页面"偶发性错乱"
- 数据更新时机不可预测
- 很难通过日志复现
实战建议:副作用管理的几条铁律
结合真实项目经验,给你几条非常实用的原则。
1. 只要和"页面可见性"有关,一定不要只用 useEffect
包括但不限于:
- 轮询请求
- WebSocket 订阅
- 定时器
- 动画启动
- 传感器监听
优先考虑 useFocusEffect。
2. 网络请求也要区分"是否仍然关心结果"
可以在 blur 时做中断或忽略。
tsx
useFocusEffect(
useCallback(() => {
let active = true;
fetchData().then(res => {
if (active) {
setData(res);
}
});
return () => {
active = false;
};
}, [])
);
3. 不要指望"页面切走了就会 unmount"
除非你明确配置了:
unmountOnBlur: true- 或使用了替代导航方案
否则,默认都还活着。
总结
在 RN 项目里,有一句话你一定要记住:
页面不在屏幕上,并不代表它已经死了。
一旦你把"页面存在"和"页面可见"混为一谈,就很容易踩到这些坑:
- 副作用偷偷运行
- 状态幽灵更新
- 性能问题随使用时间累积
- Bug 难复现、难定位
解决问题的关键,不是写更多 cleanup,而是用对生命周期维度。