@[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 行为开始失控
3. state 里出现"不该存在"的 Navigator
比如:
- 登录后,
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 已经骨折了。
四、用 Navigation State 反推架构问题的方法论
下面是一个可以直接落地的调试/评估流程。
第一步:把 Navigation State 打印出来
在根导航容器:
js
const navigationRef = createNavigationContainerRef()
<NavigationContainer
ref={navigationRef}
onStateChange={(state) => {
console.log(JSON.stringify(state, null, 2))
}}
>
{children}
</NavigationContainer>
这是所有分析的起点。
第二步:问三个核心问题
问题一:这个页面"为什么还在"
如果你在 state 里看到一个页面,问自己:
如果我现在按返回,它真的有业务意义吗?
如果答案是否定的,那就是:
- 页面职责不清
- 退出逻辑缺失
- 生命周期设计失败
问题二:这个 Navigator 解决的是什么问题
很多 Stack 的诞生过程是:
"这里逻辑有点乱,包一层 Stack 吧"
但后续没人再问:
- 它是不是还必要
- 是否可以合并
- 是否应该在流程结束后被 reset
问题三:返回链路是不是唯一的
理想状态:
任意页面,返回路径只有一条。
如果你发现:
- A 页面可能回 B
- 也可能回 C
- 取决于你是从哪跳过来的
那基本可以断定:
这是流程型页面被当成普通页面在用。
五、Demo:用 Navigation State 识别"腐化点"
场景:订单流程越改越乱
假设你有这样一个流程:
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 应该长什么样
不是越简单越好,而是越可预测越好。
它通常具备几个特征:
-
Root 层很薄
- Auth / Main / Modal
-
流程型页面生命周期清晰
- 开始创建
- 结束销毁
-
返回路径单一
- 没有"看运气返回"
-
临时 Navigator 会被 reset
- 不留下历史包袱
七、结语:导航不是工具问题,而是建模能力问题
很多 RN 项目最后变成"导航地狱",并不是因为:
- React Navigation 不好
- API 太复杂
而是因为:
开发者从来没有认真对待过"页面模型"这件事。
Navigation State 之所以重要,是因为它:
- 不参与业务争论
- 不关心你写得漂不漂亮
- 只如实反映架构质量
如果你愿意花时间读懂它、修剪它、约束它 ,
你会发现:
很多 RN 项目"不可维护"的问题,其实在导航层早就写在那了。