【共创季稿事节】鸿蒙原生 ArkTS 布局:NavRouter + NavDestination 导航布局实战
鸿蒙原生 ArkTS 布局(一):Navigation + NavRouter + NavDestination 导航布局实战
一、引言
在 HarmonyOS NEXT(API 24,对应 SDK 6.1.0)应用开发中,页面导航是每个应用的核心课题。鸿蒙 ArkUI 提供了两套导航体系:
- 页面级导航:基于
router模块(router.pushUrl、router.back),页面完全独立,需要逐个在main_pages.json注册。适合独立模块间跳转。 - 组件级导航:基于
Navigation+NavPathStack+NavDestination,在同一个页面容器内管理多个视图切换,支持栈式管理、手势返回、标题联动,适合多级下钻和详情展示。
本文以一个完整的"Navigation 导航布局演示"应用为主线,深入讲解组件级导航的核心 API、ArkTS 严格模式下的编码陷阱,以及从废弃 API 到新写法的迁移路径。
二、API 24 的 ArkTS 严格模式
API 24 引入了更严格的静态检查规则,许多传统 TypeScript 写法会直接编译报错:
| 规则 | 说明 |
|---|---|
arkts-no-any-unknown |
禁止 any 和 unknown 类型 |
arkts-no-untyped-obj-literals |
禁止未绑定显式类型的内联对象字面量 |
arkts-strict-prop-init |
禁止通过构造函数初始化 private 属性 |
这三大规则几乎贯穿了本应用开发的全过程,是我们踩坑最多的根源。
三、项目结构
entry/src/main/ets/pages/
├── Index.ets ← 入口:Navigation 根容器 + 导航列表
├── HomePage.ets ← 首页详情 NavDestination
├── DetailPage.ets ← 详情页 NavDestination(支持多层嵌套)
└── ProfilePage.ets ← 个人中心 NavDestination
只有 Index.ets 在 main_pages.json 中注册,其他三个页面是 @Component 级组件,在 Navigation 内部通过 NavDestination 加载。
四、核心 API 详解
4.1 Navigation
Navigation 是容器级组件,接收 NavPathStack 实例,管理所有子页面的压栈和出栈。
Navigation(this.navPathStack) { /* 内容区 */ }
.title('导航演示')
.mode(NavigationMode.Stack)
.hideBackButton(false)
.navDestination(this.PageMap)
mode(NavigationMode.Stack):栈式导航,每次pushPath将新页面压入栈顶hideBackButton(false):非根页面时自动显示返回箭头navDestination(this.PageMap):绑定路由名与目标页的@Builder映射
4.2 NavPathStack
路由栈管理引擎,类似于浏览器中的 history 栈。
| 方法 | 说明 |
|---|---|
pushPath(info: NavPathInfo) |
将新页面压入导航栈 |
pop() |
弹出栈顶页面 |
clear() |
清空整个导航栈,回到根页面 |
getIndexByName(name): number[] |
获取指定路由名在栈中的所有索引 |
size(): number |
当前栈深度 |
注意:
popToTop()在 API 24 中不存在,请用clear()替代。
4.3 NavPathInfo 接口
interface NavPathInfo {
name: string;
param?: Object; // 在 ArkTS 严格模式下,Object 类型不可用
}
由于 param 字段类型为 Object,而 Object 在严格模式下被禁止,因此无法通过 param 传参。替代方案:使用 getIndexByName() 获取栈索引,动态生成页面内容。
4.4 NavDestination
目标页面的容器,提供标题栏和返回事件:
NavDestination() { HomePage() }
.title('首页详情')
.onBackPressed(() => { this.navPathStack.pop(); return true; })
返回 true 表示消费返回事件,不再冒泡。
4.5 导航映射 @Builder
@Builder
PageMap(name: string, param: object) {
if (name === 'HomePage') {
NavDestination() { HomePage() }.title('首页详情')
} else if (name === 'DetailPage') {
NavDestination() { DetailPage({ navPathStack: this.navPathStack }) }.title('详情页')
} else if (name === 'ProfilePage') {
NavDestination() { ProfilePage() }.title('个人中心')
}
}
五、ArkTS 严格模式实战陷阱(重中之重)
我们在构建这个示例应用时,编译器拦截了 7 类错误。以下是逐一剖析。
5.1 NavRouter 和 onStateChange 已废弃
报错:'NavRouter' has been deprecated;'onStateChange' has been deprecated
原因:API 24 中,NavRouter 组件已经废弃,Navigation 不再需要它包裹触发路由。
解决方案:使用普通 Row、Button,在 onClick 中直接调用 pushPath:
// ❌ 旧写法(已废弃)
NavRouter() { Row() { ... } }
.onStateChange((a) => {})
.onClick(() => this.navPathStack.pushPath({ name: 'Page' }))
// ✅ 新写法
Row() { ... }
.onClick(() => {
const info: NavPathInfo = { name: 'Page' };
this.navPathStack.pushPath(info);
})
5.2 private 属性不可通过构造函数初始化
报错:Property 'navPathStack' is private and can not be initialized through the component constructor
原因:ArkTS 禁止父组件通过 Child({ privateField: value }) 给子组件的 private 属性赋值。
解决方案:将传入属性改为 @Prop:
// ❌ 错误
@Component export struct DetailPage {
private navPathStack: NavPathStack = new NavPathStack();
}
// ✅ 正确
@Component export struct DetailPage {
@Prop navPathStack: NavPathStack = new NavPathStack();
}
@Prop 允许父组件在构造时注入初始值,且值变化时子组件 UI 会响应式更新。
5.3 Object 类型被禁止
报错:Use explicit types instead of "any", "unknown" (arkts-no-any-unknown)
原因:Object 被视为 unknown 的等价形式,不允许在用户代码中出现。
解决方案:使用自定义接口替代 Object:
// ❌ 错误
const param = pathInfo[0] as Record<string, Object>;
// ✅ 正确
interface DetailPageParam { title: string; description: string; }
const param = pathInfo[0] as DetailPageParam;
虽然由于 NavPathInfo.param 本身也是 Object 类型,最终我们没有选择通过 param 传参,但自定义接口依然是 ArkTS 开发的基础技能。
5.4 内联对象字面量被禁止
报错:Object literal must correspond to some explicitly declared class or interface (arkts-no-untyped-obj-literals)
原因:编译器无法确认匿名对象是否符合目标类型约束。
解决方案:预声明类型明确的变量:
// ❌ 错误
this.navPathStack.pushPath({ name: 'HomePage' });
// ✅ 正确
const pathInfo: NavPathInfo = { name: 'HomePage' };
this.navPathStack.pushPath(pathInfo);
例外:
shadow、margin、padding等方法的对象参数通常可以从方法签名中推断类型,内联字面量可接受。
5.5 getIndexByName 返回 number[]
报错:Type 'number[]' is not assignable to type 'number'
原因:getIndexByName() 返回 number[](同一路由名可能在栈中出现多次)。
解决方案:
// ❌ 错误
const index: number = this.navPathStack.getIndexByName('DetailPage');
// ✅ 正确
const indices: number[] = this.navPathStack.getIndexByName('DetailPage');
if (indices && indices.length > 0) {
this.detailIndex = indices[indices.length - 1]; // 取栈顶索引
}
5.6 popToTop() 不存在
报错:Property 'popToTop' does not exist on type 'NavPathStack'
原因:API 24 的 NavPathStack 没有 popToTop() 方法。
解决方案:
// ❌ 错误
this.navPathStack.popToTop();
// ✅ 正确
this.navPathStack.clear();
5.7 路由参数传递的整体方案
getParamByName() 返回 Array<Object>,Object 又不可用。最终的解决方案是:放弃通过 param 传参,改用栈索引推断页面状态。
aboutToAppear(): void {
const indices: number[] = this.navPathStack.getIndexByName('DetailPage');
if (indices && indices.length > 0) {
this.detailIndex = indices[indices.length - 1];
}
if (this.detailIndex > 0) {
this.detailTitle = '子详情页 - 第 ' + (this.detailIndex + 1) + ' 层';
this.detailDescription = '这是第 ' + (this.detailIndex + 1) + ' 层详情页面...';
}
}
这种模式类似 Web 开发的 URL 路径推断——页面状态与栈位置一一对应,更可预测。
六、各页面组件详解
6.1 Index.ets 完整流程
build()
├── Navigation(navPathStack)
│ ├── Column
│ │ ├── 标题区
│ │ ├── 首页导航项 (Row + onClick → pushPath)
│ │ ├── 详情页导航项 (Row + onClick → pushPath)
│ │ ├── 个人中心导航项 (Row + onClick → pushPath)
│ │ └── 布局说明区
│ ├── .title('导航演示')
│ ├── .mode(NavigationMode.Stack)
│ ├── .hideBackButton(false)
│ └── .navDestination(PageMap)
│
└── @Builder PageMap(name, param)
├── name='HomePage' → NavDestination { HomePage() }
├── name='DetailPage' → NavDestination { DetailPage(navPathStack) }
└── name='ProfilePage' → NavDestination { ProfilePage() }
6.2 HomePage.ets
特性列表展示 Navigation 的四大优势:一体化导航、NavPathStack 路由栈、NavDestination 目标页、栈式管理。每个特性通过 FeatureItem 子组件渲染,使用 @Prop 接收外部数据。
6.3 DetailPage.ets — 多层导航核心
展示三个关键能力:
- 自动感知层级:通过
getIndexByName()获取索引,动态生成标题。 - 多层嵌套跳转:按钮反复 push 同一路由名,Navigation 自动管理栈增长和返回箭头。
- 导航控制:
pop()逐层返回,clear()一次性回根页面。
页面生命周期与栈的关系:
pushPath → aboutToAppear() → build() → [页面可见]
↓
pop() → aboutToDisappear() → [页面销毁]
同一路由名多次 push 时,旧实例保持在栈中,不会触发 aboutToDisappear。
6.4 ProfilePage.ets
展示更丰富的 UI 组合:用户信息卡片、三列统计数据(StatItem 子组件,使用 @Prop)、ForEach 循环渲染的 5 个设置项,带条件分隔线。
ForEach(this.settingList, (item: SettingItem, index: number) => {
Row() { /* 设置项内容 */ }
if (index < this.settingList.length - 1) {
Divider().height(1).color('#F0F0F0').margin({ left: 50 })
}
})
七、从 NavRouter 到 pushPath 迁移对照
| 旧写法 | 新写法 | 原因 |
|---|---|---|
NavRouter() { row } |
Row() { row } |
NavRouter 已废弃 |
onStateChange((a) => {}) |
删除,改用生命周期 | 废弃 |
内联 pushPath({ name }) |
预声明 NavPathInfo 变量 |
禁止内联对象字面量 |
pushPath({ name, param: {...} }) |
不传 param,用栈索引替代 | Object 类型被禁止 |
popToTop() |
clear() |
方法不存在 |
getParamByName() 返回 Array<Object> |
避免使用,改用 getIndexByName() |
Object 类型被禁止 |
private 传入属性 |
@Prop 装饰器 |
严格模式限制 |
八、最佳实践
架构层面
- 优先使用组件级导航。相比于
router.pushUrl,Navigation+NavPathStack更高效,天然支持手势返回和标题联动。 - 一个 Navigation 根容器。在
@Entry页面放置一个 Navigation 即可覆盖所有子页面,避免嵌套 Navigation。 - 按功能模块拆分 @Builder 分支。每个
name对应一个独立模块,模块增多时可将@Builder方法体抽离到单独文件。
编码层面
- 所有子组件传参用
@Prop,不要用private。 - 避免
Object、any、unknown,统一用自定义接口。 - 对象字面量先声明为类型变量再传入 API,尤其是在
pushPath和ForEach场景。 - 用
clear()替代popToTop()。 - 用
getIndexByName()的栈索引感知页面层级,代替路由参数传层级。
调试层面
- 在
aboutToAppear添加日志,观察栈行为:
aboutToAppear(): void {
console.info('[Page] 出现,栈深度: ' + navPathStack.size());
}
- 利用返回按钮和手势测试栈。确保
onBackPressed返回true来消费事件。
九、展望
从 API 24 的变化可以看出鸿蒙团队的方向:
- 类型安全第一。ArkTS 向完全类型安全的方言演进,
any、Object、内联字面量都会被约束。 - 声明式 + 命令式混合。
NavRouter废弃与pushPath强化,标志导航从"组件声明"走向"内容声明式,导航命令式"。 - API 持续收敛。
popToTop()移除,clear()语义统一,API 设计更简洁一致。
开发者应紧跟官方文档,在 API 升级时主动审视废弃用法,养成 ArkTS 严格模式的编码习惯。
十、完整代码关键片段
Index.ets — 根容器与导航映射
@Entry
@Component
struct Index {
private navPathStack: NavPathStack = new NavPathStack();
@Builder
PageMap(name: string, param: object) {
if (name === 'HomePage') {
NavDestination() { HomePage() }
.title('首页详情')
.onBackPressed(() => { this.navPathStack.pop(); return true; })
} else if (name === 'DetailPage') {
NavDestination() { DetailPage({ navPathStack: this.navPathStack }) }
.title('详情页')
.onBackPressed(() => { this.navPathStack.pop(); return true; })
} else if (name === 'ProfilePage') {
NavDestination() { ProfilePage() }
.title('个人中心')
.onBackPressed(() => { this.navPathStack.pop(); return true; })
}
}
build() {
Navigation(this.navPathStack) {
Column() { /* 导航列表项 */ }
}
.title('导航演示')
.mode(NavigationMode.Stack)
.hideBackButton(false)
.navDestination(this.PageMap)
}
}
main_pages.json
{ "src": ["pages/Index"] }
其他三个页面是组件级 NavDestination,不需要注册。
十一、总结
本文围绕 HarmonyOS NEXT API 24 下的一个完整 Navigation 导航布局应用,系统讲解了:
- Navigation 根容器 + NavPathStack 路由栈 + NavDestination 目标页 的核心用法
- ArkTS 严格模式的 7 类编译陷阱及其解决方案
- NavRouter 废弃后的迁移路径:从组件包裹到
pushPath命令式触发 - 组件化拆分:
@Prop传参、ForEach循环、@Builder映射的实战模式
掌握组件级导航是鸿蒙原生应用开发的基础能力。实际项目中可能遇到更复杂场景——Tab 切换、WebView 联动、跨页面状态共享、路由守卫等,这些将在后续系列文章中继续探讨。




更多推荐




所有评论(0)