返回文章列表

SwiftUI 系列 05|NavigationStack 该怎么用?页面跳转的正确打开方式

真正难的不是 push 一个页面,而是让导航状态和业务状态保持一致,不要靠 scattered 的点击动作把路由搞乱

很多人第一次用 NavigationStack,会觉得它就是 SwiftUI 版的导航容器:

  • 点一下
  • push 一个页面
  • 差不多就是这样

但真实项目里,导航真正麻烦的地方从来不是“能不能跳转”,而是:

  • 页面为什么会跳
  • 跳转状态和业务状态是否一致
  • 深链接、返回、重进页面时路径是否还能保持正确

也就是说,NavigationStack 真正要处理的,不只是界面切换,而是导航状态本身。

一、过去很多项目的导航问题,本质上都来自“跳转动作散落在各处”

在 UIKit 时代,一个很常见的写法是:

  • 某个按钮里直接 push
  • 某个回调里直接 present
  • 某个 ViewModel 回调时又通过 delegate 去跳

短期当然能跑,问题是导航逻辑会越来越散。
你很难回答:

  • 当前为什么在这个页面
  • 返回后应该回到什么状态
  • 某个业务完成后为什么会跳两层

SwiftUI 的 NavigationStack 给了一个更好的机会:
把“当前导航路径”从隐含动作,变成显式状态。

二、NavigationStack 最重要的不是容器,而是 path

很多人学 NavigationStack 时,只盯着:

  • NavigationLink
  • navigationDestination

这些当然要会,但真正应该优先理解的是:

导航在 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,而是让导航状态和业务状态能够互相解释。
只有这样,导航在项目变复杂后才不会越来越乱。