前言选择正确的导航方案在鸿蒙 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{ 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 中注册module: { routerMap: $profile:router_map }步骤三主页面与跳转实现// Index.ets (主页面) import { router } from kit.ArkUI; Entry Component struct NavigationPage { // 创建导航路径栈 navStack: NavPathStack new NavPathStack(); aboutToAppear() { // 将栈对象存入 AppStorage方便子页面获取 AppStorage.setOrCreateNavPathStack(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) } }步骤四子页面接收参数与返回// 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; }) } }// 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()解析。代码示例// Index.ets (主页面) import { router } from kit.ArkUI; Entry Component struct NavigationPage { // 创建导航路径栈 navStack: NavPathStack new NavPathStack(); aboutToAppear() { // 将栈对象存入 AppStorage方便子页面获取 AppStorage.setOrCreateNavPathStack(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) } }// 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; }) } }{ 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. 基础跳转import { router } from kit.ArkUI; // 跳转到指定页面 router.pushUrl({ url: pages/Detail, params: { id: 123 } // 传递参数 }, router.RouterMode.Standard);2. 接收参数import { router } from kit.ArkUI; Entry Component struct Detail { State id: number 0; aboutToAppear() { // 获取路由参数 const params router.getParams() as Recordstring, number; if (params) { this.id params[id]; } } }3. 页面返回// 返回上一页 router.back(); // 返回指定页面 router.back({ url: pages/Index });四、 总结Navigation 与 Router 对比特性Navigation (推荐)Router (不推荐)路由容器提供Navigation容器组件支持标题栏、工具栏联动无容器概念基于页面栈管理页面栈限制无上限支持无限跳转最大 32 层需手动清理转场动画支持自定义转场和共享元素动画仅支持简单自定义动画路由拦截支持setInterception设置拦截不支持适用场景复杂应用、多端适配、沉浸式页面简单跳转、旧项目维护最佳实践建议新项目请统一使用Navigation组件利用NavPathStack管理页面栈。参数传递传递复杂数据时务必使用JSON.stringify和JSON.parse进行序列化与反序列化避免引用丢失或属性名错乱。状态管理结合AppStorage或LocalStorage可以在不同页面间共享状态减少参数传递的复杂度。五、 架构进阶动态路由与模块解耦在大型项目中静态路由表会导致模块间强耦合。建议补充Navigation 的动态路由方案实现业务模块的彻底解耦系统路由表推荐从 API version 12 开始Navigation 支持在业务模块HSP/HAR中独立配置router_map.json。触发跳转时系统会自动完成路由模块的动态加载与组件构建无需主工程硬编码依赖。自定义路由表开发者可封装统一的路由管理模块将NavPathStack注入其中。各业务页面通过Builder和WrappedBuilder封装后注册到路由模块配合动态import()实现按需加载防止首屏加载大量代码导致卡顿。六、 高级交互路由拦截与沉浸式体验Navigation 提供了 Router 无法比拟的底层控制力可补充以下高阶 API 实战路由拦截setInterception支持在页面跳转前进行全局鉴权或状态检查。例如在跳转核心业务页前拦截判断用户是否登录未登录则重定向至登录页登录成功后自动恢复原跳转。沉浸式与自定义属性支持通过backgroundBlurStyle设置页面背景模糊或通过.hideTitleBar(true)隐藏默认标题栏配合自定义CustomNavigationBar实现完全自定义的顶部导航与沉浸式全屏体验。共享元素转场Navigation 天然支持共享元素动画可实现列表项到详情页的平滑过渡大幅提升应用的视觉连贯性Router 不支持此特性。七、 性能优化参数传递与动态加载针对原文提到的“序列化陷阱”可补充底层原理与性能优化建议引用传递 vs 深拷贝明确指出 Navigation 在传递参数时底层采用引用传递而 Router 采用深拷贝。因此对于超大对象或复杂数组Navigation 不仅避免了 JSON 序列化带来的性能损耗还能保持对象引用配合Observed实现跨页面的响应式更新。组件动态加载Router 使用Entry修饰页面模块加载时会生成全量页面而 Navigation 可配合动态加载机制仅在pushPath触发时才实例化目标组件显著降低内存占用。八、 状态管理与生命周期最佳实践补充在复杂路由场景下的状态管理范式NavPathStack 的共享范式除了AppStorage更推荐使用Provide/Consume在组件树内共享NavPathStack这能确保路由状态与 UI 树的强绑定避免内存泄漏并保证状态更新的实时性。精准的生命周期监听Navigation 提供了比 Router 更细粒度的页面生命周期。建议开发者利用onShown/onHidden处理页面可见性相关的业务逻辑如暂停/恢复视频播放、刷新数据利用onReady获取NavDestinationContext进行安全的参数解析与栈对象绑定。全局路由监听通过uiObserver.on(navDestinationUpdate)注册全局监听可实现统一的路由埋点、页面停留时长统计等无侵入式监控。