技术分享

鸿蒙 Navigation 怎么用?路径栈管理、Tab 页内嵌导航,跟 Router 有什么不同

前面讲了 Router 页面跳转和 Preferences 本地存储,列表跳详情、收藏存本地这套链路跑通了。

Router 在小项目里够用。但页面一多,比如底部有三个 Tab,每个 Tab 里还有子页面来回切,Router 的路径栈管理就开始吃力——哪个页面在栈顶、返回时回到哪一层,代码多了容易绕晕。

Navigation 是鸿蒙后来推的组件级导航方案,专门解决复杂页面结构下的路由问题。不管你是跟着教程自学,还是在重庆找鸿蒙培训系统学习,Navigation 都是必过的门槛——实际项目里多 Tab 应用是主流,Router 处理不了的场景,Navigation 能搞定。本篇把它的路径栈管理、Tab 嵌套导航、跟 Router 的适用场景一次讲清楚。

15.1 为什么 Router 不够用了

看这个场景:底部导航栏,三个 Tab——首页、发现、我的。从首页点进文章详情,再点进作者主页,然后切到“发现”Tab 看了一眼,再切回“首页”时,刚才打开的文章详情和作者主页都还在吗?

用 Router 管理的话,pushUrl 一路往下推,返回时用 back 一个一个退。但如果用户在几个 Tab 之间来回切,每个 Tab 的页面栈怎么保存、怎么恢复,Router 本身不管这些。需要自己在代码里用数组或 Map 存每个 Tab 的路径栈,切换时手动恢复——写起来很繁琐。

Navigation 的解决思路:给每个 Tab 分配一个独立的子导航栈,切 Tab 时各自的页面状态自动保留。

15.2 Navigation 路由原理

Navigation 是鸿蒙最核心的路由组件,既可以作为应用全局的导航容器,也可以在弹窗、模态等局部场景中使用。

Navigation 的组成结构

Navigation 由三部分构成:导航栏(NavBar)、子页(NavDestination)和页面栈管理器(NavPathStack)。

三种显示模式

Navigation 支持三种显示模式,能自动根据屏幕尺寸切换布局:

页面栈管理

Navigation 的子页面栈存在 NavPathStack 中,NavPathStack 跟 Navigation 一一对应。在每个子页中可以通过 NavDestination 的 onReady 回调获取 NavPathStack,也可以全局维护一个单例,在任意地方获取并执行路由操作。

15.3 Navigation 基础跳转:从搭建到跑通

第一步:创建 NavPathStack 并绑定 Navigation

@Component
struct BasicNavigation {
  stack: NavPathStack = new NavPathStack()

  build() {
    Navigation(this.stack) {
      // Navigation 作为路由根容器,可以不显示任何内容
    }
  }
}

第二步:定义子页面

每个子页面需要包裹在 NavDestination 中,并声明对外的实例化方法:

// BasicNavDest.ets
@Builder
export function PageOneBuilder() {
  BasicNavDestination({ name: 'pageOne' });
}

@Component
struct 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)

15.4 NavDestination 的标题栏和工具栏

NavDestination 支持设置页面标题和底部工具栏。

NavDestination() {
  // 页面内容
}
.title('单标题演示')
.title({
  main: '主标题演示',
  sub: '副标题演示'
})
.toolbarConfiguration([
  { value: "A" },
  { value: "B" },
  { value: "C" },
  { value: "D" },
  { value: "E" },
])

标题栏位于内容区上部,工具栏位于内容区下部。重复设置同一属性,后设置的会覆盖前者。

15.5 Tab 页里怎么嵌套子导航

三个 Tab,各自有独立的页面栈。Navigation 用 NavPathStack 的分组能力实现。

@Entry
@Component
struct 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 里,切回来直接恢复。

15.6 Navigation 和 Router,使用场景

定位

Navigation:组件级导航容器,可作全局或局部导航。

Router:页面级全局路由,页面需在 main_pages.json 中注册。

显示模式

Navigation:单栏、分栏、自适应三种,能根据屏幕尺寸自动切换。

Router:仅全屏跳转。

路由操作

Navigation:pushPath、pushDestination(带返回回调)、pop、replacePath、clear 等,支持栈的精细操作。

Router:pushUrl、back、replaceUrl,栈操作相对简单。

参数传递

Navigation:NavPathInfo 对象传参,通过 onReady 或 getParamByIndex 获取,支持返回传参。

Router:params 传参,通过 getParams 获取。

转场动画

Navigation:自定义动效、共享元素转场。

Router:基本动画,自定义能力有限。

生命周期

Navigation:NavDestination 组件事件管理,支持路由拦截。

Router:UIObserver.on('routerPageUpdate') 监听,拦截能力弱。

适用场景

Navigation:中大型应用、多端适配、有底部 Tab 或侧边栏的结构。

Router:简单线性页面流,开发门槛低。

新项目、有底部 Tab、页面结构复杂,用 Navigation。单线跳转的小项目 Router 依然能用,但官方重心在 Navigation 上。

15.7 实战应用:用 Navigation 改造文章列表跳转详情页

把上一篇的列表-详情-收藏功能,从 Router 改成 Navigation。

主页面:Navigation 包裹,底部两个 Tab

@Entry
@Component
struct 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,点文章跳详情

@Component
struct ArticleListContent {
  @Prop navStack: NavPathStack;

  build() {
    List() {
      ListItem() {
        Text('文章标题')
          .onClick(() => {
            this.navStack.pushPath({ name: 'DetailPage', param: { articleId: '001' } });
          })
      }
    }
  }
}

15.8 本篇总结

常见问题

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,比跟着教程学印象深得多。