RN 项目中“页面存在 ≠ 页面可见”会导致哪些隐藏 Bug?


网罗开发 (小红书、快手、视频号同名)

大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括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 来看一个典型页面的生命周期。

常见生命周期触发顺序

  1. 页面第一次进入

    • 组件 mount
    • useEffect(() => {}, []) 执行
    • 页面 focus
  2. 跳转到下一个页面

    • 当前页面 blur
    • 组件仍然 mounted
  3. 返回当前页面

    • 页面重新 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,而是用对生命周期维度。

相关推荐
tongyue3 小时前
天问esp32驱动DHT11官方程序BUG修复
bug
赵财猫._.1 天前
React Native鸿蒙开发实战(十):鸿蒙NEXT深度适配与未来展望
react native·react.js·harmonyos
2401_860319521 天前
在React Native鸿蒙跨平台开发采用分类网格布局,通过paramRow和paramLabel/paramValue的组合展示关键配置信息
react native·react.js·harmonyos
2301_796512521 天前
使用如Redux、MobX或React Context等状态管理库来管理状态,React Native鸿蒙跨平台开发来实战
react native·react.js·harmonyos
洞窝技术1 天前
自建 React Native 热修复,让线上事故 30 秒“归零”
react native
沛沛老爹1 天前
Web开发者快速上手AI Agent:基于提示工程的旅游攻略系统实战
前端·人工智能·ai·agent·react·旅游攻略
旧梦星轨1 天前
掌握 Vite 环境配置:从 .env 文件到运行模式的完整实践
前端·前端框架·node.js·vue·react
2401_860494701 天前
在React Native中实现鸿蒙跨平台开发中开发一个运动类型管理系统,使用React Navigation设置应用的导航结构,创建一个堆栈导航器
react native·react.js·harmonyos
2301_796512521 天前
使用状态管理、持久化存储或者利用现有的库来辅助React Native鸿蒙跨平台开发开发一个允许用户撤销删除的操作
javascript·react native·react.js