鸿蒙深入解读 HarmonyOS NEXT 声明式路由:NavRouter + NavDestination 实战
深入解读 HarmonyOS NEXT 声明式路由:NavRouter + NavDestination 实战



一、引言
HarmonyOS NEXT 是华为完全剥离 Android 代码、基于 OpenHarmony 演进的纯血操作系统。从 API 24 开始,ArkUI 框架全面转向声明式 UI 范式,其中页面路由是应用架构中最基础也最关键的能力。
过去,开发者习惯使用 router.pushUrl() 命令式跳转——每个页面独立运行在不同上下文,传参和回调需要大量样板代码。在 API 24 中,推荐的做法是声明式路由:所有页面集中在一个组件树中声明,由框架自动完成创建、显示、隐藏与销毁。
本文将从一个可运行的完整示例出发,逐层拆解 NavRouter + NavDestination 的技术细节和实践要点。
二、声明式路由整体架构
声明式路由的核心组件是**Navigation**,它像一个舞台管理者:
┌─────────────────────────────────────┐
│ Navigation │
│ ┌──────────┬────────────────────┐ │
│ │ 导航栏 │ NavDestination │ │
│ │ ┌────┐ │ 目标页面内容 │ │
│ │ │首页 │ │ │ │
│ │ │个人 │ │ │ │
│ │ │设置 │ │ │ │
│ │ └────┘ │ │ │
│ └──────────┴────────────────────┘ │
└─────────────────────────────────────┘
四个核心角色:
| 组件 | 职责 |
|---|---|
Navigation |
路由外壳容器 |
NavRouter |
可点击的导航触发器 |
NavDestination |
目标页面容器 |
NavPathStack |
页面栈管理器 |
关键区别:声明式路由不创建新 Page 实例,由 Navigation 在同一组件树中按需构建和切换子页面,切换动画由框架免费提供。
三、NavPathStack——路由的「大脑」
NavPathStack 是可观察的状态容器,管理页面栈中的每个条目。
3.1 创建
@Entry
@Component
struct Index {
@State
pathStack: NavPathStack = new NavPathStack();
}
@State 保证栈变化时 Navigation 自动重新渲染。
3.2 核心 API
| 方法 | 功能 |
|---|---|
pushPath(info) |
推入一个页面 |
pushPathByName(name, param) |
按名称推入,可携带参数 |
pop() |
返回上一页 |
popToName(name) |
弹到指定名称 |
popToIndex(index) |
弹到指定索引 |
clear() |
清空栈 |
getAllPathName() |
获取所有页面名称 |
size() |
获取栈深度 |
示例中,每个 NavRouter 点击时调用:
.onClick(() => {
this.pathStack.pushPath({ name: 'HomePage' });
})
四、Navigation——外壳容器配置
4.1 模式
Navigation(this.pathStack)
.mode(NavigationMode.Stack)
| 模式 | 行为 | 适用场景 |
|---|---|---|
Stack |
导航栏 + 页面栈 | 侧边栏 + 内容区 |
Split |
分栏并排 | 平板、大屏 |
Auto |
自适应 | 多端适配 |
4.2 导航栏
Navigation(this.pathStack)
.title('导航菜单')
.navBarWidth('100%')
4.3 navDestination——核心映射
Navigation(this.pathStack) { /* 导航栏内容 */ }
.navDestination(this.pageMap)
navDestination 接收 @Builder 函数,框架在每次 pushPath 时根据 name 创建对应 NavDestination。
五、@Builder 页面映射详解
@Builder 扮演路由表的角色:
@Builder
pageMap(name: string, param: Object) {
if (name === 'HomePage') {
NavDestination() {
HomePage({ pathStack: this.pathStack })
}
.title('首页')
.backgroundColor('#f5f5f5')
.onShown(() => console.info('首页已显示'))
.onHidden(() => console.info('首页已隐藏'))
} else if (name === 'ProfilePage') {
NavDestination() { ProfilePage() }
.title('个人中心')
}
}
参数传递
HomePage({ pathStack: this.pathStack })
子页面可通过 this.pathStack?.pop() 手动返回,比 router.back() 更灵活。
生命周期钩子
| 钩子 | 触发时机 |
|---|---|
onShown |
页面显示时 |
onHidden |
页面隐藏时 |
onAppear |
组件挂载时 |
onDisAppear |
组件卸载时 |
声明式路由的页面是「显示/隐藏」而非「创建/销毁」,切换轻量,状态自然保持。
六、NavRouter 详解
NavRouter 是用户导航的入口,API 24 中已标记 deprecated,推荐用 Row + onClick 替代。
6.1 示例
NavRouter() {
Row() {
Text('🏠').fontSize(24)
Column() {
Text('首页').fontSize(16)
Text('应用主页面').fontSize(12).fontColor('#999')
}
}
.padding(20)
}
.onClick(() => {
this.pathStack.pushPath({ name: 'HomePage' });
})
6.2 推荐替代
Row() { Text('导航项') }
.onClick(() => this.pathStack.pushPath({ name: 'TargetPage' }))
优势:样式自由、无嵌套开销。
七、NavDestination 详解
7.1 基本使用
NavDestination() {
HomePage()
}
.title('首页')
.backgroundColor('#f5f5f5')
7.2 标题栏控制
.hideTitleBar(true)
.backButtonIcon($r('app.media.back'))
.onBackPressed(() => false) // true 消费返回事件
7.3 内置转场动画
框架自动为 NavDestination 进出应用平滑动画,无需额外配置。
八、示例架构分析
8.1 文件组织
所有逻辑集中在 Index.ets。真实项目推荐拆分:
pages/
├── Index.ets ← 主入口 + @Builder pageMap
├── HomePage.ets ← 首页
├── ProfilePage.ets ← 个人中心
├── SettingsPage.ets ← 系统设置
└── AboutPage.ets ← 关于
8.2 状态分层
| 层级 | 状态 | 管理方式 |
|---|---|---|
| 路由级 | pathStack |
Index 的 @State |
| 页面级 | message / isNotify / isDark |
各子组件 @State |
8.3 参数传递路径
Index (NavPathStack)
├─ pushPath('HomePage') → HomePage({ pathStack })
│ └─ pathStack?.pop()
├─ pushPath('ProfilePage') → ProfileRow({ label, value })
└─ pushPath('SettingsPage') → Toggle {...}
九、子组件与 @Prop 传参
@Component
struct ProfileRow {
@Prop label: string = '';
@Prop value: string = '';
build() {
Row() {
Text(this.label).fontSize(14).fontColor('#666')
Text(this.value).fontSize(14).fontColor('#333')
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding({ top: 10, bottom: 10 })
}
}
关键:必须使用对象字面量传参:
// ✅ 正确
ProfileRow({ label: '用户名', value: 'AtomCode' })
// ❌ 编译错误
ProfileRow('用户名', 'AtomCode')
十、命令式 vs 声明式路由
10.1 代码量对比
命令式:
router.pushUrl({ url: 'pages/Second', params: { key: 'value' } });
// 目标页用 router.getParams() 读取
声明式(集中声明):
this.pathStack.pushPath({ name: 'Second', param: { key: 'value' } });
NavDestination() { SecondPage({ data: param['key'] }) }
10.2 状态保持
| 关注点 | 命令式 | 声明式 |
|---|---|---|
| 页面切换 | 新建 Page 实例 | 隐藏/显示 NavDestination |
| 状态保持 | 手动管理 | 天然保持 |
| 动画过渡 | 手动配置 | 内置 |
| 传参 | getParams() 全局读取 |
组件参数传入 |
10.3 适用场景
声明式:侧边栏布局、多 Tab 切换、页面间紧密交互。
命令式:独立页面(登录→主页)、Deep Link、跨 Ability。
十一、最佳实践
11.1 pageMap 优化
超过 10 个页面用 switch:
@Builder
pageMap(name: string, param: Object) {
switch (name) {
case 'HomePage': break;
case 'ProfilePage': break;
default: // 404
}
}
11.2 返回时状态重置
NavDestination()
.onAppear(() => { this.isNotify = true; this.isDark = false; })
11.3 栈深度控制
建议不超过 10 层。过深时用 popToName 清理中间层。
十二、API 12 → API 24 迁移
| API 版本 | 用法 |
|---|---|
| API 12 | NavRouter().destination() 链式声明 |
| API 24 | NavRouter deprecated,推荐 onClick + @Builder |
迁移步骤
NavRouter→Row+onClick.destination()中 NavDestination → 移到@Builder pageMappushPathByName→pushPath- 利用新增生命周期和动画属性
十三、进阶:自定义过渡动画
Navigation(this.pathStack).transition({
duration: 350,
curve: Curve.FastOutSlowIn,
type: NavigationTransitionType.Push,
slide: { edge: SlideEdge.Right, distance: '100%' }
})
NavDestination 级别:
NavDestination().transition({
enter: TransitionEffect.slide(SlideEdge.Right),
exit: TransitionEffect.fade
})
十四、总结
| 维度 | 评价 |
|---|---|
| 学习曲线 | 中等,API 设计直观 |
| 可维护性 | 优秀,集中声明,状态流清晰 |
| 性能 | 良好,按需构建,内置动画 |
| 灵活性 | 较高,生命周期、动画均可定制 |
| 适用规模 | 中小到大型应用均可 |
核心思想:状态驱动视图而非「跳转命令」。当你在 @Builder pageMap 中声明 NavDestination 时,你是在为应用的页面关系拓扑编程,而非编排跳转序列。
附录:快速体验
- DevEco Studio 中新建 HarmonyOS NEXT 项目(API 24)
- 替换
Index.ets为完整示例代码 - 运行应用,点击菜单项观察页面切换
更多推荐



所有评论(0)