前言:选择正确的导航方案
在鸿蒙 ArkTS 开发中,页面跳转与导航是应用骨架的核心。目前 ArkUI 提供了两套路由方案:
- Navigation 组件(推荐):基于组件化的路由容器,适用于绝大多数场景,特别是需要复杂交互、多端适配(一次开发,多端部署)的应用。
- Router 模块(不推荐):基于页面路径的跳转方式,功能较基础,页面栈有上限(32层),主要用于简单的页面跳转或兼容旧代码。
本文将重点讲解官方推荐的 Navigation 组件,并简要对比 Router 模块。
一、 Navigation 组件:组件级路由的新标准
Navigation 是一个路由导航的根视图容器,它支持单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式,能够根据窗口大小自动切换布局,非常适合折叠屏和平板设备。
核心概念:
- NavPathStack:导航路径栈,用于管理页面的入栈(Push)、出栈(Pop)和替换(Replace)。
- NavDestination :子页面容器,必须嵌套在
Navigation组件中使用。
1. 基础路由配置与跳转
要实现 Navigation 路由,首先需要配置路由表,并在 module.json5 中注册。
步骤一:配置路由表 (router_map.json)
在 src/main/resources/base/profile 目录下创建 router_map.json:
python
{
"routerMap": [
{
"name": "pageOne",
"pageSourceFile": "src/main/ets/pages/PageOne.ets",
"buildFunction": "PageOneBuilder"
},
{
"name": "pageTwo",
"pageSourceFile": "src/main/ets/pages/PageTwo.ets",
"buildFunction": "PageTwoBuilder"
}
]
}

注:buildFunction 是页面组件对应的 @Builder 函数名称。
步骤二:在 module.json5 中注册
python
"module": {
"routerMap": "$profile:router_map"
}

步骤三:主页面与跳转实现
TypeScript
// Index.ets (主页面)
import { router } from '@kit.ArkUI';
@Entry
@Component
struct NavigationPage {
// 创建导航路径栈
navStack: NavPathStack = new NavPathStack();
aboutToAppear() {
// 将栈对象存入 AppStorage,方便子页面获取
AppStorage.setOrCreate<NavPathStack>('navStack', this.navStack);
}
build() {
Navigation(this.navStack) {
// 导航页内容(首页内容)
Column({ space: 20 }) {
Text('这是首页')
.fontSize(30)
Button('跳转到 PageOne')
.onClick(() => {
// 方式1:通过名称跳转,可携带参数
this.navStack.pushPath({ name: 'pageOne', param: '我是首页传来的参数' });
})
Button('跳转到 PageTwo (带回调)')
.onClick(() => {
// 方式2:带返回回调的跳转
this.navStack.pushPathByName('pageTwo', '参数2', (popInfo) => {
console.info('PageTwo 返回了:' + JSON.stringify(popInfo.result));
});
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
.mode(NavigationMode.Stack) // 设置为单栏模式
.title('主标题')
.hideTitleBar(false)
}
}

步骤四:子页面接收参数与返回
TypeScript
// src/main/ets/pages/PageOne.ets
// 1. Builder 函数:作为路由入口
// name 是路由名称(通常用不到),param 是传入的参数
@Builder
export function PageOneBuilder(name: string, param: Object) {
// 【修正点】:只传入 value,去掉不存在的 name 属性
// 注意:如果 param 可能是 undefined,建议做一下类型转换或判空
PageOne({ value: param as string });
}
@Component
export struct PageOne {
navPathStack: NavPathStack = new NavPathStack();
// 定义接收参数的属性
@State value: string = '';
// 【新增】:在组件初始化时处理参数
aboutToAppear() {
// 这里可以做一些额外的初始化逻辑
console.info('PageOne 已加载,参数为: ' + this.value);
}
build() {
NavDestination() {
Column({ space: 20 }) {
Text(`接收到的参数: ${this.value}`)
.fontSize(24)
.fontWeight(FontWeight.Bold)
Button('返回上一页')
.onClick(() => {
// 普通返回
this.navPathStack.pop();
})
Button('返回并带回数据')
.backgroundColor('#007DFF')
.fontColor(Color.White)
.onClick(() => {
// 带结果返回,触发上一页的回调
this.navPathStack.pop({ result: '我是PageOne带回的数据' });
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
.title('PageOne')
// onReady 主要用于获取 Context 和 PathStack,不建议在这里做业务数据赋值
.onReady((ctx: NavDestinationContext) => {
this.navPathStack = ctx.pathStack;
})
}
}
TypeScript
// src/main/ets/pages/PageTwo.ets
@Builder
export function PageTwoBuilder(name: string, param: string) {
PageTwo({ value: param });
}
@Component
export struct PageTwo {
value: string = '';
@State showValue: string = '';
private navPathStack: NavPathStack = new NavPathStack();
build() {
NavDestination() {
Column({ space: 20 }) {
Text(`PageTwo 收到: ${this.showValue}`)
.fontSize(24)
Button('返回并触发回调')
.backgroundColor('#E65555')
.onClick(() => {
// 这里触发的 result 会回到 Index.ets 的 pushPathByName 回调中
this.navPathStack.pop({ result: 'PageTwo 任务完成' });
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
.title('PageTwo')
.onReady((ctx: NavDestinationContext) => {
this.navPathStack = ctx.pathStack;
this.showValue = ctx.pathInfo?.param as string || '无参数';
})
}
}


二、 参数传递进阶:对象与数组的序列化陷阱
在使用 Navigation 或 Router 进行页面跳转传参时,如果传递的是复杂对象或数组,经常会遇到数据丢失或无法刷新的问题。
问题原因:
路由传参在底层会经历"序列化 + 反序列化"的过程。对于被 @ObservedV2 和 @Trace 装饰的类对象,序列化后属性名会被添加 __ob_ 前缀,导致反序列化后失去观察能力,甚至属性名错乱。
解决方案:
- 基础类型/简单对象:直接传递,接收时注意类型断言。
- 复杂对象/数组 :建议使用
JSON.stringify()序列化后传递,接收方再用JSON.parse()解析。
代码示例:
TypeScript
// Index.ets (主页面)
import { router } from '@kit.ArkUI';
@Entry
@Component
struct NavigationPage {
// 创建导航路径栈
navStack: NavPathStack = new NavPathStack();
aboutToAppear() {
// 将栈对象存入 AppStorage,方便子页面获取
AppStorage.setOrCreate<NavPathStack>('navStack', this.navStack);
}
build() {
Navigation(this.navStack) {
// 导航页内容(首页内容)
Column({ space: 20 }) {
Text('这是首页')
.fontSize(30)
Button('跳转到 PageOne')
.onClick(() => {
// 方式1:通过名称跳转,可携带参数
this.navStack.pushPath({ name: 'pageOne', param: '我是首页传来的参数' });
})
Button('跳转到 PageTwo (带回调)')
.onClick(() => {
// 方式2:带返回回调的跳转
this.navStack.pushPathByName('pageTwo', '参数2', (popInfo) => {
console.info('PageTwo 返回了:' + JSON.stringify(popInfo.result));
});
})
Button('跳转到文章页 (传数组)')
.onClick(() => {
// 发送方逻辑:准备数组并序列化
let titles: string[] = ["文章1", "文章2", "文章3"];
// 将数组序列化为字符串传递
this.navStack.pushPathByName("articlePage", JSON.stringify(titles));
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
.mode(NavigationMode.Stack) // 设置为单栏模式
.title('主标题')
.hideTitleBar(false)
}
}
TypeScript
// src/main/ets/pages/ArticlePage.ets
// 1. 路由入口 Builder(必须加上,否则路由找不到页面)
@Builder
export function ArticlePageBuilder(name: string, param: Object) {
// 将路由传来的参数传给组件的 initialData 属性
ArticlePage({ initialData: param as string });
}
@Component
export struct ArticlePage {
// 接收路由传来的 JSON 字符串
initialData: string = '';
@State buttonTitles: string[] = [];
private navPathStack: NavPathStack = new NavPathStack();
// 2. 在 aboutToAppear 中解析参数(比 onReady 更安全规范)
aboutToAppear() {
if (this.initialData) {
try {
this.buttonTitles = JSON.parse(this.initialData) as string[];
} catch (e) {
console.error('参数解析失败:', e);
}
}
}
build() {
NavDestination() {
Column() {
Text('接收到的文章列表:').fontSize(24).margin({ bottom: 20 })
ForEach(this.buttonTitles, (title: string) => {
Button(title).margin(10)
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
.title('文章页面')
.onReady((ctx: NavDestinationContext) => {
this.navPathStack = ctx.pathStack;
})
}
}
TypeScript
{
"routerMap": [
{
"name": "pageOne",
"pageSourceFile": "src/main/ets/pages/PageOne.ets",
"buildFunction": "PageOneBuilder"
},
{
"name": "pageTwo",
"pageSourceFile": "src/main/ets/pages/PageTwo.ets",
"buildFunction": "PageTwoBuilder"
},
{
"name": "articlePage",
"pageSourceFile": "src/main/ets/pages/ArticlePage.ets",
"buildFunction": "ArticlePageBuilder"
}
]
}


三、 Router 模块:传统路由方式
Router 模块位于 @kit.ArkUI 中,适用于简单的页面跳转。虽然官方不再推荐作为首选,但在某些轻量级场景下依然有用。
1. 基础跳转
TypeScript
import { router } from '@kit.ArkUI';
// 跳转到指定页面
router.pushUrl({
url: 'pages/Detail',
params: { id: 123 } // 传递参数
}, router.RouterMode.Standard);
2. 接收参数
TypeScript
import { router } from '@kit.ArkUI';
@Entry
@Component
struct Detail {
@State id: number = 0;
aboutToAppear() {
// 获取路由参数
const params = router.getParams() as Record<string, number>;
if (params) {
this.id = params['id'];
}
}
}
3. 页面返回
TypeScript
// 返回上一页
router.back();
// 返回指定页面
router.back({ url: 'pages/Index' });
四、 总结:Navigation 与 Router 对比
| 特性 | Navigation (推荐) | Router (不推荐) |
|---|---|---|
| 路由容器 | 提供 Navigation 容器组件,支持标题栏、工具栏联动 |
无容器概念,基于页面栈管理 |
| 页面栈限制 | 无上限,支持无限跳转 | 最大 32 层,需手动清理 |
| 转场动画 | 支持自定义转场和共享元素动画 | 仅支持简单自定义动画 |
| 路由拦截 | 支持 setInterception 设置拦截 |
不支持 |
| 适用场景 | 复杂应用、多端适配、沉浸式页面 | 简单跳转、旧项目维护 |
最佳实践建议:
- 新项目 :请统一使用
Navigation组件,利用NavPathStack管理页面栈。 - 参数传递 :传递复杂数据时,务必使用
JSON.stringify和JSON.parse进行序列化与反序列化,避免引用丢失或属性名错乱。 - 状态管理 :结合
AppStorage或LocalStorage可以在不同页面间共享状态,减少参数传递的复杂度。