我项目里现在的写法是这样的——Navigation 包着首页内容,NavDestination 做子页面:
Navigation 里面直接写首页布局,子页面通过 NavDestination 定义,由navDestination属性做路由映射。这个模式用了大半年,没什么大问题。
但有个别扭的地方:首页内容必须写在 Navigation 的大括号里,不能是一个独立的 NavDestination。如果我想让首页也享受 NavDestination 的一些能力(比如标题栏配置、生命周期回调),就得在 Navigation 里面再套一层自定义组件,结构变得奇怪。
我试过在 Navigation 的大括号里放一个自定义组件HomePage(),然后在HomePage里再写 Tabs。功能上没问题,但调试的时候 DevEco 的组件树会多一层嵌套,看着不太舒服。
这意味着什么?首页也可以是一个 NavDestination 了。Navigation 不再强制要求在大括号里直接写首页布局,而是可以把主页当作路由栈的一部分来管理。
这个变化对于复杂应用的主页结构来说挺有用的。比如记账 App 首页有 Tabs,如果 Tabs 本身是一个 NavDestination,那它的标题栏、工具栏都能用 NavDestination 的标准能力来配。

虽然新写法在 API 20 Beta3 才出来,但现有的 Navigation + NavPathStack 模式依然好用。我分享一下我目前项目里的结构,以及在哪些地方做了优化。
所有子页面的路由都在这一个 Builder 里映射,方便维护。新增页面只需要加一行 case。
我一开始是每个模块有自己的 pageMap,后来发现合并到一个里面更方便——全局搜索路由名直接就能定位到页面组件。二十几个路由放在一起,找个 case 也就是一眼的事。
pushReplacement比较常用——登录成功后跳到主页,但不想让用户按返回键回到登录页,就用 replace 而不是 push。
removeByName也有个实用场景:用户从列表进详情再进编辑页,保存成功后想把详情和编辑都清掉直接回列表。这时候removeByName(EditPage)+pop()就行。
换个场景。如果做宝宝记录 App,主页可能是一个时间线 + 统计的 Tabs 结构。用 Navigation 的方式一样:
工具类 App 也类似——主页放功能入口,子页面用 NavDestination 承载具体功能。
Navigation 的页面传参我之前也纠结过,整理一下我用过的几种方式:
适合多个页面需要共享同一份数据的场景——比如记账 App 里当前选中分类在列表页和筛选页都要用。
我一般在列表 → 详情这种场景用pushPathByName传参,跨模块共享用@Provide/@Consume,全局配置用 AppStorage。三种方式各有适用场景,不冲突。
一个 Navigation 对应一个 NavPathStack 实例。子页面必须通过 @Consume 拿到同一个实例,不能自己 new。我一开始犯过这个错,pushPathByName 之后页面没跳转,查了半天才发现是两个不同的栈实例……
当时还以为是 pushPathByName 的参数写错了,把路由名打印出来核对了好几遍。后来在子页面里打this.navPathStack.getSize(),发现是 0——这才意识到这个栈和首页的不是同一个。

有次我在按钮的 onClick 里连续调了两次pushPathByName——想跳过一个中间页直接到目标页。结果行为很诡异,有时候只跳了一层,有时候两层都跳了但动画卡住了。后来改成先 push 再removeByName中间页,效果稳定多了。
Navigation 的结构在 API 20 Beta3 又进了一步,首页也能用 NavDestination 来管理了。对于结构简单的应用差别不大,但如果首页本身比较复杂(多层 Tabs、动态内容切换),新写法让主页和子页面在架构上更统一。
对了,如果你现在的项目还是 Router 模式,强烈建议切到 Navigation。Router 的 push/pop 在复杂场景下维护成本太高了,NavPathStack 配合系统路由表用起来顺手很多。我之前迁移的时候也犹豫过,迁完之后确实觉得值。