从 Navigation State 反推架构腐化

@[toc

------为什么导航状态,往往是 RN 项目里最早"变味"的地方

如果你做过中大型 RN 项目,大概率会遇到一种情况:

项目明明还能跑,但你已经不太敢动导航相关的代码了。

加一个页面要想半天,

改一个返回逻辑要全局搜 goBack

线上 bug 说"返回错页",你只能本地反复点。

这不是你不熟 React Navigation,而是项目的"页面模型"已经开始腐化了。

而最早暴露腐化信号的地方,往往不是 UI、不是业务逻辑,而是------
Navigation State。

这篇文章,我们就反过来:

不从"怎么写导航",而是从 Navigation State 的异常形态,一步步反推出 RN 项目正在发生的架构问题。

一、Navigation State 是什么,它为什么这么"诚实"

先明确一个认知:

Navigation State 是当前 App 页面世界的完整快照。

在 React Navigation 中,一个典型的 state 长这样:

js 复制代码
{
  type: 'stack',
  key: 'root-stack',
  index: 1,
  routes: [
    { name: 'Home', key: 'Home-xxx' },
    {
      name: 'Profile',
      key: 'Profile-yyy',
      state: {
        type: 'tab',
        index: 0,
        routes: [
          { name: 'Info' },
          { name: 'Settings' }
        ]
      }
    }
  ]
}

它包含了:

  • 当前有哪些页面
  • 页面嵌套关系
  • 当前焦点在哪
  • 哪些 Navigator 还"活着"

它不关心你怎么组织代码,只忠实记录"现在页面真实长什么样"。

也正因为这样:

Navigation State 是不会撒谎的。

二、什么叫"架构腐化",在导航层长什么样

很多人一听"架构腐化",会觉得很抽象。

但在 RN 项目里,它在导航层的表现非常具体。

下面这些现象,你一定见过。

1. State 越看越长,看不懂层级

刚开始项目时:

text 复制代码
RootStack
 ├─ Login
 └─ MainTabs

半年后变成:

text 复制代码
RootStack
 ├─ Login
 ├─ MainTabs
 │   ├─ HomeStack
 │   │   ├─ Home
 │   │   ├─ Detail
 │   │   └─ DetailModal
 │   ├─ ProfileStack
 │   │   ├─ Profile
 │   │   ├─ Edit
 │   │   └─ EditConfirm
 │   └─ WebStack
 │       ├─ Web
 │       └─ WebModal
 └─ TempStack
     ├─ Guide
     └─ Promo

你会发现:

  • Navigator 一层套一层
  • 有些 Stack 是"临时加的"
  • 有些页面其实业务已经下线,但导航还在

这不是业务复杂,而是页面边界已经失控。

2. 同一个页面名,在 State 里出现多次

这是一个非常危险的信号。

你在 state 里看到:

js 复制代码
routes: [
  { name: 'Detail', key: 'Detail-1' },
  { name: 'Detail', key: 'Detail-2' }
]

这通常意味着:

  • 页面被当成"流程节点"在用
  • 开发者不知道该 push 还是 replace
  • 没有页面唯一职责的概念

结果就是:

  • 返回逻辑不可预测
  • 页面栈越走越深
  • Android Back 行为开始失控

比如:

  • 登录后,AuthStack 还留在 state 里
  • 新手引导结束,GuideStack 只是被 navigate 覆盖
  • 弹窗用 Stack 实现,但永远不被清理

这些 Navigator 不是"不可见",而是"还活着"。

它们会在:

  • 返回时突然被激活
  • 状态恢复时重新出现
  • 热更新后引发幽灵页面

三、Navigation State 为什么会最先暴露问题

这是 RN 项目一个很有意思的现象。

1. 业务可以靠条件判断"苟住"

业务代码里你可以写:

js 复制代码
if (!data) return null

或者:

js 复制代码
if (userType === 'A') { ... }

很多混乱,其实被 if else 掩盖了。

2. 但导航是"结构性"的

导航不是条件渲染,它是结构决策

  • 页面是不是还在栈里
  • Navigator 是否被卸载
  • 返回链路是否单一

这些问题,一旦设计错了,就会长期存在于 State 里

3. 所以 State 就像"X 光片"

UI 看着正常,但 State 已经骨折了。

下面是一个可以直接落地的调试/评估流程

在根导航容器:

js 复制代码
const navigationRef = createNavigationContainerRef()

<NavigationContainer
  ref={navigationRef}
  onStateChange={(state) => {
    console.log(JSON.stringify(state, null, 2))
  }}
>
  {children}
</NavigationContainer>

这是所有分析的起点。

第二步:问三个核心问题

问题一:这个页面"为什么还在"

如果你在 state 里看到一个页面,问自己:

如果我现在按返回,它真的有业务意义吗?

如果答案是否定的,那就是:

  • 页面职责不清
  • 退出逻辑缺失
  • 生命周期设计失败

很多 Stack 的诞生过程是:

"这里逻辑有点乱,包一层 Stack 吧"

但后续没人再问:

  • 它是不是还必要
  • 是否可以合并
  • 是否应该在流程结束后被 reset
问题三:返回链路是不是唯一的

理想状态:

任意页面,返回路径只有一条。

如果你发现:

  • A 页面可能回 B
  • 也可能回 C
  • 取决于你是从哪跳过来的

那基本可以断定:

这是流程型页面被当成普通页面在用。

场景:订单流程越改越乱

假设你有这样一个流程:

复制代码
List → Detail → Pay → Result

一开始你写的是:

js 复制代码
navigation.navigate('Detail')
navigation.navigate('Pay')
navigation.navigate('Result')

State 会变成:

js 复制代码
[List, Detail, Pay, Result]

后来需求来了:

  • 支付失败要回 Pay
  • 从订单列表也能直接进 Result

于是你开始混用:

js 复制代码
navigate
push
replace
goBack

半年后 State 可能是:

js 复制代码
[List, Detail, Pay, Detail, Result]

此时你已经很难只靠代码理解返回逻辑了。

改造思路:流程页面 = 状态机,而不是栈

正确做法是:

  • 流程结束,直接 reset
  • 流程中禁止"自由返回"
js 复制代码
navigation.reset({
  index: 0,
  routes: [{ name: 'Result' }]
})

这样 State 会被主动修剪

六、一个成熟 RN 项目,Navigation State 应该长什么样

不是越简单越好,而是越可预测越好

它通常具备几个特征:

  1. Root 层很薄

    • Auth / Main / Modal
  2. 流程型页面生命周期清晰

    • 开始创建
    • 结束销毁
  3. 返回路径单一

    • 没有"看运气返回"
  4. 临时 Navigator 会被 reset

    • 不留下历史包袱

七、结语:导航不是工具问题,而是建模能力问题

很多 RN 项目最后变成"导航地狱",并不是因为:

  • React Navigation 不好
  • API 太复杂

而是因为:

开发者从来没有认真对待过"页面模型"这件事。

Navigation State 之所以重要,是因为它:

  • 不参与业务争论
  • 不关心你写得漂不漂亮
  • 只如实反映架构质量

如果你愿意花时间读懂它、修剪它、约束它

你会发现:

很多 RN 项目"不可维护"的问题,其实在导航层早就写在那了。

相关推荐
码农很忙2 小时前
HSAP一体化混合搜索与分析架构全解:重塑数据价值的新范式
架构
前端程序猿之路2 小时前
Next.js 入门指南 - 从 Vue 角度的理解
前端·vue.js·语言模型·ai编程·入门·next.js·deepseek
大布布将军3 小时前
⚡️ 深入数据之海:SQL 基础与 ORM 的应用
前端·数据库·经验分享·sql·程序人生·面试·改行学it
川贝枇杷膏cbppg3 小时前
Redis 的 RDB 持久化
前端·redis·bootstrap
JIngJaneIL3 小时前
基于java+ vue农产投入线上管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
天外天-亮3 小时前
v-if、v-show、display: none、visibility: hidden区别
前端·javascript·html
jump_jump4 小时前
手写一个 Askama 模板压缩工具
前端·性能优化·rust
be or not to be4 小时前
HTML入门系列:从图片到表单,再到音视频的完整实践
前端·html·音视频
子春一24 小时前
Flutter 2025 架构演进工程体系:从单体到模块化,构建可扩展、可协作、可持续的大型应用
flutter·架构