SwiftUI 系列 05|NavigationStack 该怎么用?页面跳转的正确打开方式
真正难的不是 push 一个页面,而是让导航状态和业务状态保持一致,不要靠 scattered 的点击动作把路由搞乱
很多人第一次用 NavigationStack,会觉得它就是 SwiftUI 版的导航容器:
- 点一下
- push 一个页面
- 差不多就是这样
但真实项目里,导航真正麻烦的地方从来不是“能不能跳转”,而是:
- 页面为什么会跳
- 跳转状态和业务状态是否一致
- 深链接、返回、重进页面时路径是否还能保持正确
也就是说,NavigationStack 真正要处理的,不只是界面切换,而是导航状态本身。
一、过去很多项目的导航问题,本质上都来自“跳转动作散落在各处”
在 UIKit 时代,一个很常见的写法是:
- 某个按钮里直接 push
- 某个回调里直接 present
- 某个 ViewModel 回调时又通过 delegate 去跳
短期当然能跑,问题是导航逻辑会越来越散。
你很难回答:
- 当前为什么在这个页面
- 返回后应该回到什么状态
- 某个业务完成后为什么会跳两层
SwiftUI 的 NavigationStack 给了一个更好的机会:
把“当前导航路径”从隐含动作,变成显式状态。
二、NavigationStack 最重要的不是容器,而是 path
很多人学 NavigationStack 时,只盯着:
NavigationLinknavigationDestination
这些当然要会,但真正应该优先理解的是:
导航在 SwiftUI 里不是纯动作,而是可以被表达成状态路径。
这意味着你不再只是“点击时跳一下”,而是在维护:
- 当前路径里有哪些页面
- 哪个业务状态对应哪个目的地
- 某个操作后路径该如何变化
一旦从这个角度看,导航就不再只是 UI 事件,而开始变成状态流的一部分。
三、很多导航写乱,不是 API 不会,而是业务状态和路由状态没对齐
举个很常见的例子:
- 用户选中一篇文章
- 文章详情页应该出现
- 删除文章后,详情页又该怎么退
如果你只是把导航理解成“点了就进”,那很多后续问题会开始变乱:
- 深链接进来时怎么恢复路径
- 数据失效后路径怎么调整
- 返回时列表应该保持什么状态
所以导航真正难的地方,是它不应该脱离业务状态单独存在。
更稳的思路是:
- 导航路径和业务状态相互可解释
- 当前为什么在这个页面,可以从状态上说得清
四、NavigationLink 很方便,但项目一复杂就不能只靠它表达全部导航
NavigationLink 特别适合直接、局部、简单的跳转,比如:
- 列表点进详情
- 设置页进子设置
但项目一复杂,如果全部导航都靠分散的 NavigationLink 堆出来,很快会遇到:
- 路径难统一管理
- 深链接难接
- 某些流程跳转不再是单纯点击驱动
- 返回路径和业务状态脱节
这不是说 NavigationLink 不好,而是它更适合承担局部入口,不适合独自承载整个应用的导航策略。
五、一个更实用的思路:把“页面目的地”设计成业务可解释的值
很多项目导航变乱,根源之一是:
- 跳转只被写成一堆 UI 动作
- 没有形成可推理的目的地模型
更稳的方式通常是让路径里承载的不是“某次点击”,而是“当前业务想去哪个目的地”。
例如:
- articleDetail(id: String)
- userProfile(id: String)
- settings
这样导航就更像状态,而不是散乱的动作集合。
它的好处是:
- 路径可以被重建
- 深链接更自然
- 测试和调试也更容易描述
六、真实项目里最常见的坏味道:View、ViewModel、路由动作互相缠在一起
很多 SwiftUI 导航代码最后会长成这样:
- View 里写一部分
NavigationLink - ViewModel 里又偷偷决定跳转
- 某个 service 回调又触发导航状态变化
最后没人能说清:
- 谁是导航的真正拥有者
- 哪个状态决定当前路径
- 某个返回动作是用户行为还是业务行为
导航一旦进入这种状态,后面新增一个深链接、补一个恢复路径,成本都会明显上升。
七、一个实用判断:当前页面是“因为点进来的”,还是“因为状态决定它该在这里”
这是我自己很常问的一句话。
如果一个页面的出现,只能解释成:
- 因为刚刚点了某个按钮
那通常说明导航还过于动作驱动。
更理想的解释应该是:
- 因为当前路径状态包含了它
- 因为当前业务上下文决定应该展示它
这个差别很重要。
前者更像临时动作,后者更像可维护的导航状态。
八、结论:NavigationStack 真正让你管理的,不只是跳转,而是路径状态
如果只用一句话总结,我会说:
NavigationStack真正重要的地方,不是终于能在 SwiftUI 里 push 页面,而是它让导航从“散落的点击动作”更自然地收口成“可被管理的路径状态”。
所以页面跳转的正确打开方式,不只是会写 NavigationLink,而是让导航状态和业务状态能够互相解释。
只有这样,导航在项目变复杂后才不会越来越乱。