上一篇讲了 Router 页面跳转和 Preferences 本地存储,列表跳详情、收藏存本地这套链路跑通了。
Router 在小项目里够用。但页面一多,比如底部有三个 Tab,每个 Tab 里还有子页面来回切,Router 的路径栈管理就开始吃力——哪个页面在栈顶、返回时回到哪一层,代码多了容易绕晕。
Navigation 是鸿蒙后来推的组件级导航方案,专门解决复杂页面结构下的路由问题。不管你是跟着教程自学,还是在重庆找鸿蒙培训系统学习,Navigation 都是必过的门槛——实际项目里多 Tab 应用是主流,Router 处理不了的场景,Navigation 能搞定。本篇把它的路径栈管理、Tab 嵌套导航、跟 Router 的适用场景一次讲清楚。
看这个场景:底部导航栏,三个 Tab——首页、发现、我的。从首页点进文章详情,再点进作者主页,然后切到“发现”Tab 看了一眼,再切回“首页”时,刚才打开的文章详情和作者主页都还在吗?
用 Router 管理的话,pushUrl 一路往下推,返回时用 back 一个一个退。但如果用户在几个 Tab 之间来回切,每个 Tab 的页面栈怎么保存、怎么恢复,Router 本身不管这些。需要自己在代码里用数组或 Map 存每个 Tab 的路径栈,切换时手动恢复——写起来很繁琐。
Navigation 的解决思路:给每个 Tab 分配一个独立的子导航栈,切 Tab 时各自的页面状态自动保留。
Navigation 是鸿蒙最核心的路由组件,既可以作为应用全局的导航容器,也可以在弹窗、模态等局部场景中使用。
Navigation 的组成结构
Navigation 由三部分构成:导航栏(NavBar)、子页(NavDestination)和页面栈管理器(NavPathStack)。
NavBar:Navigation 自带的导航栏,可以隐藏,也可以用自定义内容替代。
NavDestination:每个子页面的根容器,所有需要挂载到 Navigation 上的页面都要包裹在 NavDestination 中。它除了支持通用组件属性外,还支持页面生命周期、标题栏、工具栏、自定义转场动画、横竖屏控制等能力。
NavPathStack:管理子页面切换的核心对象,每个 Navigation 都需要绑定一个 NavPathStack。它提供 pushPath、pushDestination(带返回回调)、pop、replacePath、clear 等方法,以及路由拦截、转场动画控制、路由栈信息获取等能力。
三种显示模式
Navigation 支持三种显示模式,能自动根据屏幕尺寸切换布局:
Stack(单栏):宽度 < 600vp 时使用,全屏显示单个页面。
Split(分栏):宽度 >= 600vp 时使用,左侧导航栏 + 右侧内容区。
Auto(自适应):自动在 Stack 和 Split 之间切换,一套代码适配手机和平板。
页面栈管理
Navigation 的子页面栈存在 NavPathStack 中,NavPathStack 跟 Navigation 一一对应。在每个子页中可以通过 NavDestination 的 onReady 回调获取 NavPathStack,也可以全局维护一个单例,在任意地方获取并执行路由操作。

第一步:创建 NavPathStack 并绑定 Navigation
@Componentstruct BasicNavigation {stack: NavPathStack = new NavPathStack()build() {Navigation(this.stack) {// Navigation 作为路由根容器,可以不显示任何内容}}}
第二步:定义子页面
每个子页面需要包裹在 NavDestination 中,并声明对外的实例化方法:
// BasicNavDest.ets@Builderexport function PageOneBuilder() {BasicNavDestination({ name: 'pageOne' });}@Componentstruct BasicNavDestination {name: string = 'NA'build() {NavDestination() {// 页面内容}}}
第三步:配置路由表
在 entry/src/main/resources/base/profile/ 目录下创建 route_map.json:
{"routerMap": [{"name": "basicPageOne","pageSourceFile": "src/main/ets/pages/BasicFeature/BasicNavDest.ets","buildFunction": "PageOneBuilder"}]}
在 module.json5 中注册路由表:
{"module": {"routerMap": "$profile:route_map"}}
第四步:跳转页面
this.stack.pushPath({ name: 'basicPageOne' });返回上一页:
this.stack.popPath();对于没有分栏需求的应用,建议将 Navigation 的导航栏隐藏,基于 NavDestination 组件进行所有页面功能的开发:
Navigation(this.stack) {// ...}.hideNavBar(true)
NavDestination 支持设置页面标题和底部工具栏。
NavDestination() {// 页面内容}.title('单标题演示').title({main: '主标题演示',sub: '副标题演示'}).toolbarConfiguration([{ value: "A" },{ value: "B" },{ value: "C" },{ value: "D" },{ value: "E" },])
标题栏位于内容区上部,工具栏位于内容区下部。重复设置同一属性,后设置的会覆盖前者。
三个 Tab,各自有独立的页面栈。Navigation 用 NavPathStack 的分组能力实现。
@Entry@Componentstruct TabNavigationDemo {private homeStack: NavPathStack = new NavPathStack();private discoverStack: NavPathStack = new NavPathStack();private profileStack: NavPathStack = new NavPathStack();@State currentTabIndex: number = 0;build() {Column() {if (this.currentTabIndex === 0) {Navigation(this.homeStack) {HomeContent({ navStack: this.homeStack })}} else if (this.currentTabIndex === 1) {Navigation(this.discoverStack) {DiscoverContent({ navStack: this.discoverStack })}} else {Navigation(this.profileStack) {ProfileContent({ navStack: this.profileStack })}}Row() {Button('首页').onClick(() => { this.currentTabIndex = 0; })Button('发现').onClick(() => { this.currentTabIndex = 1; })Button('我的').onClick(() => { this.currentTabIndex = 2; })}.width('100%').height(56).justifyContent(FlexAlign.SpaceAround)}}}
每个 Tab 对应一个独立的 NavPathStack。切到“发现”Tab 时,之前“首页”Tab 里打开的页面都保留在 homeStack 里,切回来直接恢复。
新项目、有底部 Tab、页面结构复杂用 Navigation。单线跳转的小项目 Router 依然能用,但官方重心在 Navigation 上。
把上一篇的列表-详情-收藏功能,从 Router 改成 Navigation。
主页面:Navigation 包裹,底部两个 Tab
@Entry@Componentstruct MainNavigationPage {private articleStack: NavPathStack = new NavPathStack();private favoriteStack: NavPathStack = new NavPathStack();@State currentTab: number = 0;build() {Column() {if (this.currentTab === 0) {Navigation(this.articleStack) {ArticleListContent({ navStack: this.articleStack })}} else {Navigation(this.favoriteStack) {FavoriteListContent({ navStack: this.favoriteStack })}}Row() {Button('文章').onClick(() => { this.currentTab = 0; })Button('收藏').onClick(() => { this.currentTab = 1; })}.width('100%').height(56).justifyContent(FlexAlign.SpaceAround)}}}
文章列表子组件:接收 navStack,点文章跳详情
@Componentstruct ArticleListContent {@Prop navStack: NavPathStack;build() {List() {ListItem() {Text('文章标题').onClick(() => {this.navStack.pushPath({ name: 'DetailPage', param: { articleId: '001' } });})}}}}
Navigation 是鸿蒙主推方案:组件级导航容器,支持分栈管理、三种显示模式、自定义动效。
核心结构:NavBar + NavDestination + NavPathStack,三者配合完成页面管理。
基础跳转:创建 NavPathStack → 绑定 Navigation → 配置路由表 → pushPath 跳转。
Tab 导航:每个 Tab 分配独立 NavPathStack,切换时状态自动保留。
Q: Navigation 是鸿蒙一开始就有的吗?A: 不是。Navigation 是后续版本推出的,用来解决 Router 在多 Tab、复杂导航场景下的不足。现在官方主推 Navigation。
Q: Navigation 和 Router 怎么选?A: 新项目、有底部 Tab、页面结构复杂、需要多端适配用 Navigation。简单线性跳转的小项目 Router 依然能用。
Q: 用 Navigation 之后,Router 代码要全改吗?A: 跳转写法从 pushUrl/back 换成 pushPath/popPath,传参方式类似。逻辑不用大改,主要是把 Navigation 容器搭起来。
Q: Tab 切换时怎么保留每个 Tab 的页面状态?A: 每个 Tab 分配独立的 NavPathStack,切换时栈不销毁,切回来页面状态还在。
学完 Navigation,页面跳转和本地存储两条线都实现了。列表到详情、收藏持久化、Tab 分栈导航——这些组合起来,一个 App 的骨架基本成型。文章列表现在还是模拟数据,接下来解决数据获取。下一篇讲网络请求,用 http 模块从服务器拉真实数据,async/await 处理异步,让 App 真正“活”起来。
如果你在自学过程中遇到问题,或者想找个重庆本地的鸿蒙培训系统学一遍,重庆华鸿科技正在做鸿蒙技术培训,跟赛力斯、阿维塔、重钢集团有深度合作,培训内容对接企业实际用人需求。会用真实项目把组件、路由、存储、网络请求这些零散知识点串起来——模块之间的衔接、数据的流转、问题的 debug,比跟着教程学印象深得多。
《从零到一学鸿蒙原生开发》(点击跳转):
《从零到一:鸿蒙原生应用开发实战》第12篇:Flex 弹性布局——ArkUI 有没有 CSS 一样的弹性布局?
《从零到一:鸿蒙原生应用开发实战》第13篇:List 组件详解——ForEach 循环渲染与 LazyForEach 懒加载
《从零到一:鸿蒙原生应用开发实战》第14篇:页面跳转和本地存储——列表页到详情页,收藏退出不丢失
本文由「华鸿技术栈」原创发布,专注鸿蒙(HarmonyOS)原生应用与元服务开发。从零到一分享实战代码与项目案例,对接鸿蒙技术培训资源与上市公司研发岗位,为开发者铺平入行之路。不讲虚的,只上干货。转载请联系授权。