本节目标
· 理解页面路由在应用开发中的作用,掌握 router 模块的基本概念与核心 API
· 掌握 router.pushUrl 与 router.replaceUrl 两种跳转模式的区别与适用场景
· 了解 router 的命名路由与页面返回机制
· 掌握 Navigation 组件与 NavPathStack 的配合使用,理解组件级路由与页面级路由的本质区别
· 掌握 router 与 Navigation 两种路由方案的参数传递与返回值处理方法
· 掌握 pageTransition 页面转场动画与 customNavContentTransition Navigation 自定义转场动画的实现方式
· 能够根据项目架构选择合适的路由方案,并独立完成多页面应用的导航功能开发
一、路由导航概述
1.1 什么是页面路由
页面路由指在应用程序中实现不同页面之间的跳转和数据传递。Router 模块通过不同的 url 地址,可以方便地进行页面路由,轻松地访问不同的页面。
在创建项目时,src/main/ets/entryability 目录下会生成 EntryAbility.ts,在 src/main/ets/pages 目录下会生成 Index 页面,EntryAbility 的 onWindowStageCreate 方法中指定了应用的入口页面。当需要从入口页面跳转到其他页面时,就需要使用路由能力。
1.2 两套路由方案
当前 HarmonyOS 支持两套路由机制(Navigation 和 Router),Navigation 作为后续长期演进及推荐的路由选择方案。
Router(页面级路由) :每个页面独立配置、通过 @Entry 修饰,页面之间相互隔离,使用成本低,适合新手理解页面切换,也适合一些简单应用。
Navigation(组件级路由) :页面作为组件嵌套在 Navigation 容器内,天然具备标题栏、工具栏、返回逻辑的联动能力。Navigation 是路由导航的根视图容器,一般作为页面的根容器。
二、Router 页面路由
2.1 导入与核心 API
使用 Router 需要先从 @kit.ArkUI 模块中导入:
typescript
import { router } from '@kit.ArkUI';
核心 API 如下:
· router.pushUrl(options: RouterOptions):跳转到指定页面
· router.replaceUrl(options: RouterOptions):替换当前页面
· router.back(options?: RouterOptions):返回上一页面或指定的页面
· router.clear():清空所有历史页面,仅保留当前页面记录
2.2 pushUrl 与 replaceUrl 的区别
pushUrl 将目标页面压入页面栈,当前页面保留在栈中,可以通过返回键或调用 router.back() 方法返回到当前页。replaceUrl 用目标页面替换当前页面,当前页面从栈中移除,返回时直接回到当前页的上一页。
以一个完整流程为例------首页 → 登录 → 个人中心:
typescript
// 首页 Index.ets
import { router } from '@kit.ArkUI';
@Entry
@Component
struct Index {
@State message: string = '首页';
@State isLogin: boolean = true;
build() {
Column() {
Button("个人中心").onClick(() => {
if (this.isLogin) {
router.pushUrl({ url: 'pages/Person' }) // 已登录,直接进个人中心
} else {
router.pushUrl({ url: 'pages/Login' }) // 未登录,先进登录页
}
})
}
}
}
typescript
// 登录页 Login.ets
import { router } from '@kit.ArkUI';
@Entry
@Component
struct Login {
build() {
Column({ space: 10 }) {
Button("提交").onClick(() => {
// router.pushUrl({ url: "pages/Person" }); // 首页 - 登录页 - 个人中心页 - 返回:首页
router.replaceUrl({ url: "pages/Person" }); // 首页 -(登录页被替换成个人中心页)- 返回:首页
})
}
}
}
关键理解:使用 pushUrl 时,返回路径是"个人中心→登录页→首页"三步;使用 replaceUrl 时,登录页被个人中心页替换,返回路径只有"个人中心→首页"两步。
2.3 命名路由
Router 支持为页面设置别名,通过命名路由进行跳转。在目标页面的 @Entry 装饰器中配置 routeName:
typescript
@Entry({ routeName: 'myPage' })
@Component
struct MyPage {
// ...
}
跳转时使用名称而非路径:
typescript
router.pushUrl({ url: 'myPage' })
返回命名路由页面时,也可指定名称:
typescript
router.back({ url: 'myPage' }) // myPage 为返回的命名路由页面别名
2.4 页面返回前增加询问框
Router 提供了 router.showAlertBeforeBackPage 能力,在用户点击返回按钮时弹出询问框,确认后再返回。这在表单未保存等场景中非常实用。
三、Navigation 组件导航
3.1 Navigation 的整体架构
Navigation 是路由导航的根视图容器,一般作为页面的根容器。Navigation 组件主要包含三个部分:
NavBar(导航栏) :Navigation 的子组件,直接挂载到 Navigation 上,可以通过 hideNavBar 属性进行隐藏(单栏应用推荐隐藏导航页)。导航栏不存在页面栈中。
NavPathStack(导航控制器) :Navigation 的子页面栈存在 NavPathStack 中,每个 Navigation 都需要绑定一个 NavPathStack 对象,用于控制 Navigation 中所有子页的切换。NavPathStack 提供了很多基础的路由切换方法,如 pushPath、pushDestination、pop、replacePath 等,以及路由拦截、转场动画控制、路由栈信息获取等能力。
NavDestination(子页容器) :Navigation 子页面的根容器,每个子页面都需要包裹在一个 NavDestination 中,通过 NavPathStack 提供的栈操作方法将子页面挂载到 Navigation 上显示或删除。NavDestination 除了支持普通组件的通用属性外,还支持页面相关的属性,如页面的生命周期、页面工具栏和标题栏、自定义页面转场动画等能力。
3.2 Navigation 的显示模式
Navigation 包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式。Auto 模式会基于 Navigation 组件的宽度自动在 Stack(< 600vp)和 Split(>= 600vp)中切换。将 mode 属性设置为 NavigationMode.Stack,Navigation 组件即可设置为单页面显示模式。
3.3 路由操作
使用 Navigation 进行路由操作,首先需要创建 NavPathStack 对象并作为构造入参传给 Navigation 组件,以实现二者的绑定:
typescript
@Component
struct BasicNavigation {
stack: NavPathStack = new NavPathStack()
build() {
Navigation(this.stack) {
// Navigation 作为路由根容器,可以不显示任何内容
}
}
}
常用的路由操作包括:
· pushPathByName(name, param):将 name 指定的 NavDestination 页面信息入栈,传递的数据为 param
· pushPathByName(name, param, onPop):带返回回调的跳转,添加 onPop 回调接收入栈页面出栈时的返回结果
· pop():页面出栈
· popToName(name) / popToIndex(index):返回到指定页面
· replacePath(info) / replacePathByName(name, param):路由替换
· moveToTop(name):将指定页面移动到栈顶
· clear():清空路由栈
跳转到新页面时:
typescript
this.pageStack.pushPathByName('pageOne', null) // 跳转页面时不携带参数
this.pageStack.pushPathByName('PageOne', "PageOne Param") // 携带参数跳转
四、参数传递与返回值
4.1 Router 的参数传递
传递参数:跳转时使用 RouterOptions 的 params 参数传递一个对象:
typescript
router.pushUrl({
url: 'pages/Detail',
params: {
id: 1001,
title: 'HarmonyOS 入门',
price: 99
}
})
接收参数:在目标页面中通过 router.getParams() 获取传递过来的参数对象:
typescript
import { router } from '@kit.ArkUI';
interface DetailParams {
id: number
title: string
price: number
}
@Entry
@Component
struct Detail {
@State params: DetailParams = router.getParams() as DetailParams
build() {
Column() {
Text(`商品:${this.params.title}`)
Text(`价格:¥${this.params.price}`)
}
}
}
返回值:在目标页面通过 router.back({ params: { ... } }) 传递返回参数,上一页在 onPageShow 回调中通过 router.getParams() 获取返回值。
4.2 Navigation 的参数传递
Navigation 的路由跳转 API(pushPath、pushPathByName、pushDestination、pushDestinationByName)支持参数传递。跳转时 pushPath 传入 name 和 param,目标 NavDestination 从 pathInfo.param 读取并先做类型校验。
传递参数:
typescript
this.pageStack.pushPathByName('Detail', {
itemId: 1001,
title: 'HarmonyOS 入门'
})
接收参数:页面新创建时,推荐在 NavDestination 的 onReady 生命周期中处理参数:
typescript
@Component
struct DetailPage {
@State itemId: number = 0
@State title: string = ''
build() {
NavDestination() {
Column() {
Text(`商品ID:${this.itemId}`)
Text(`商品名称:${this.title}`)
}
}
.onReady((context: NavDestinationContext) => {
const param = context.pathInfo.param as Record<string, Object>
this.itemId = param.itemId as number
this.title = param.title as string
})
}
}
返回值:pop 返回场景下,自 API 15 起推荐开发者使用 onResult 处理返回场景的路由参数。也可以在 pushPathByName 时添加 onPop 回调:
typescript
this.pageStack.pushPathByName('PageOne', "PageOne Param", (popInfo) => {
// popInfo 对象保存着详情页传过来的数据
console.log('返回的数据:', popInfo.result)
})
重要建议:参数只放轻量 ID 或筛选条件,大对象和页面状态留在数据层。大对象、可变业务对象或需要进程恢复的数据,建议只传稳定 ID,再由目标页从数据库或状态仓读取,避免页面栈持有过期对象。
4.3 两种方案参数传递的核心区别
Navigation 传递参数性能更优,Navigation 通过引用传递,Router 通过深拷贝完成。因此传递大对象时 Navigation 的性能优势尤为明显。
五、页面转场动画
5.1 pageTransition 页面间转场
两个页面间发生跳转,一个页面消失,另一个页面出现,这时可以配置各自页面的页面转场参数实现自定义的页面转场效果。页面间转场效果写在 pageTransition 函数中,通过 PageTransitionEnter 和 PageTransitionExit 指定页面进入和退出的动画效果。
typescript
// page A
pageTransition() {
PageTransitionEnter({ type: RouteType.None, duration: 1200, curve: Curve.Linear })
.slide(SlideEffect.Left) // 从左侧滑入
PageTransitionExit({ type: RouteType.None, duration: 1200, curve: Curve.Linear })
.slide(SlideEffect.Right) // 向右侧滑出
}
其中 type 参数用于区分页面转场的类型:
· RouteType.None (默认值):对页面栈的 push、pop 操作均生效
· RouteType.Push :仅在页面入栈(pushUrl)时生效
· RouteType.Pop :仅在页面出栈(back / pop)时生效
以 push 操作为例:通过 pushUrl 从页面 A 跳转到页面 B,则页面 A 退出做退场动画,页面 B 进入做入场动画。通过 back 从页面 B 返回到页面 A,则页面 B 退出做退场动画,页面 A 进入做入场动画。
可通过 slide、translate、scale、opacity 属性定义不同的页面转场效果。对于 PageTransitionEnter 而言,这些效果表示入场时的起点值;对于 PageTransitionExit 而言,这些效果表示退场的终点值。
5.2 Navigation 自定义转场动画
Navigation 自定义转场动画由 customNavContentTransition 事件提供,适用于控制 Navigation 内所有页面的场景,统一转场动画效果。调用 customNavContentTransition 方法并返回实现的转场协议对象,若返回 undefined,则使用系统默认转场。
typescript
Navigation(this.pageStack) {
// ...
}
.customNavContentTransition((from: NavContentInfo, to: NavContentInfo, operation: NavigationOperation) => {
// 首页不进行自定义动画
if (from.index === -1 || to.index === -1) {
return undefined
}
// 根据 operation 判断 push 还是 pop,分别定义不同的转场动画
// ...
})
要把动画生命周期交给 Navigation,应使用 customNavContentTransition:在 transition(proxy) 中启动动画,在 animateTo 的 onFinish 中调用一次 proxy.finishTransition(),再通过 onTransitionEnd(success) 接收整个 Navigation 转场的结果。
Navigation 转场用 customNavContentTransition(系统管生命周期,不黑屏),组件显隐用 transition,属性变化用 animateTo,三者各司其职,不要对同一属性重复设动画。
六、Router 与 Navigation 的选型建议
6.1 核心区别对比
架构层面:Router 是页面级路由,每个页面独立配置、通过 @Entry 修饰,页面之间相互隔离;Navigation 是组件级路由,页面作为组件嵌套在 Navigation 容器内,天然具备标题栏、工具栏、返回逻辑的联动能力。
能力层面:Navigation 天然支持一多(一次开发多端部署),Router 不支持;Navigation 没有路由数量限制,Router 限制 32 个;Navigation 可以获取到路由栈 NavPathStack,并对路由栈进行操作;Navigation 可以嵌套在模态对话框中,Router 不支持;Navigation 的组件全量由开发者自行控制,可以自定义复杂的动效和属性设置,Router 的 page 对象不对外暴露。
性能层面:Navigation 传递参数通过引用传递,Router 通过深拷贝完成;Navigation 可以配合动态加载,实现组件动态加载,Router 页面使用 @Entry 进行修饰,当前模块加载时会生成全量页面。
6.2 选型原则
官方推荐 Navigation 作为首选路由方案,Router 后续不再演进新功能。
如果项目只有一个主项目,没有拆分模块,直接用 Navigation 即可。如果项目包含模块(比如项目有主项目,A、B 模块),主项目的 H 界面要跳转到 A 模块的 I 界面,或者 A 模块的 I 界面要跳转到 B 模块的 J 界面,这个时候就要用 Router。
选型总结:
· 小型简单应用:可直接使用 Router,接入成本低,无需额外学习,基础的 push、back 操作能满足需求
· 中大型应用:推荐使用 Navigation + NavPathStack,具备更强的路由栈操作能力、更好的参数传递性能和更灵活的自定义能力
· 跨模块跳转场景:需要使用 Router 实现跨模块的页面跳转
七、多元化习题
习题 1(判断题)
题目:在 ArkTS 中,router.replaceUrl 会将目标页面压入页面栈,当前页面保留在栈中,可以通过返回键返回到当前页。
答案:错误
解读:router.pushUrl 将目标页面压入页面栈,当前页面保留在栈中;router.replaceUrl 用目标页面替换当前页面,当前页面从栈中移除。题目描述的是 pushUrl 的行为,而非 replaceUrl。
习题 2(单选题)
题目:以下关于 Navigation 中 NavPathStack 的说法,正确的是( )
A. NavPathStack 是 Navigation 的导航栏组件
B. 每个 Navigation 只能绑定一个 NavPathStack 对象
C. NavPathStack 只能用于 push 操作,不支持 pop 操作
D. NavPathStack 不能获取路由栈信息
答案:B
解读:NavPathStack 是导航控制器,不是导航栏组件,选项 A 错误。每个 Navigation 都需要绑定一个 NavPathStack 对象,选项 B 正确。NavPathStack 提供了 pushPath、pop、replacePath 等多种路由操作方法,选项 C 错误。NavPathStack 支持路由拦截、转场动画控制、路由栈信息获取等能力,选项 D 错误。
习题 3(多选题)
题目:关于 Router 与 Navigation 的对比,以下说法正确的有(多选):
A. Navigation 传递参数通过引用传递,Router 通过深拷贝完成
B. Navigation 没有路由数量限制,Router 限制 32 个
C. Navigation 天然支持一多,Router 不支持
D. Router 可以获取到路由栈对象,Navigation 不支持
答案:A、B、C
解读:Navigation 传递参数性能更优,通过引用传递;Router 通过深拷贝完成,选项 A 正确。Navigation 没有路由数量限制,Router 限制 32 个,选项 B 正确。Navigation 天然支持一多,Router 不支持,选项 C 正确。Navigation 可以获取到路由栈对象 NavPathStack,Router 不支持获取路由栈对象,选项 D 的描述正好相反。
习题 4(代码填空题)
题目:请补全以下代码,使页面从 Index 跳转到 Detail 页面,并传递商品 ID 和名称参数。
typescript
import { router } from '@kit.ArkUI';
@Entry
@Component
struct Index {
build() {
Column() {
Button('查看详情').onClick(() => {
router.pushUrl({
url: 'pages/Detail',
// 在此处填写代码,传递 id 和 title 参数
______________
})
})
}
}
}
答案:params: { id: 1001, title: 'HarmonyOS 入门' }
解读:Router 通过 RouterOptions 的 params 属性传递参数,可以传递任意类型的对象。在目标页面中,通过 router.getParams() 方法获取传递过来的参数对象,再通过属性名访问具体值。
习题 5(代码改错题)
题目:以下代码存在错误,请指出问题并修正。
typescript
@Component
struct DetailPage {
build() {
NavDestination() {
Column() {
Text('详情页')
}
}
.onReady((context: NavDestinationContext) => {
const param = context.pathInfo.param as Record<string, Object>
// 直接使用 param.itemId 访问参数
console.log(param.itemId)
})
}
}
答案:代码本身没有语法错误,但存在一个潜在风险------context.pathInfo.param 返回的是 Object 类型,直接访问 param.itemId 在 ArkTS 的严格类型系统下可能报错。应先进行类型断言或类型校验:
typescript
.onReady((context: NavDestinationContext) => {
const param = context.pathInfo.param as Record<string, Object>
const itemId = param.itemId as number // 先断言为具体类型
console.log(itemId)
})
解读:Navigation 的参数传递中,目标 NavDestination 从 pathInfo.param 读取参数时应先做类型校验。ArkTS 是强类型语言,对 Object 类型的属性访问需要先进行类型断言,否则可能编译不通过。
习题 6(简答题)
题目:简述 Router 的 pushUrl 和 replaceUrl 两种跳转模式的区别,并结合"首页 → 登录页 → 个人中心页"的场景说明各自的页面栈变化和返回路径。
答案:pushUrl 将目标页面压入页面栈,当前页面保留在栈中;replaceUrl 用目标页面替换当前页面,当前页面从栈中移除。在"首页 → 登录页 → 个人中心页"场景中,使用 pushUrl 时,页面栈变化为 首页, 登录页, 个人中心页,返回路径是"个人中心页 → 登录页 → 首页"。使用 replaceUrl 时,登录页被个人中心页替换,页面栈变化为 首页, 个人中心页,返回路径是"个人中心页 → 首页"。
解读:选择哪种跳转模式取决于业务需求。如果用户需要能够返回到被替换的页面(如登录页),使用 pushUrl;如果被替换的页面不需要再被访问(如登录成功后不需要返回登录页),使用 replaceUrl 可以保持页面栈的简洁。
八、本节知识点总结
路由导航概述
页面路由实现不同页面间的跳转和数据传递。HarmonyOS 提供两套路由方案:Router(页面级路由)和 Navigation(组件级路由),Navigation 为官方推荐方案。
Router 页面路由
使用 router.pushUrl 压入页面栈,router.replaceUrl 替换当前页面,router.back 返回,router.clear 清空历史。支持命名路由和返回前询问框。
Navigation 组件导航
包含 NavBar、NavPathStack、NavDestination 三大部分。NavPathStack 管理页面栈,支持 pushPath、pop、replacePath 等操作。支持单栏、分栏、自适应三种显示模式。
参数传递
Router 通过 params 传递,router.getParams() 接收,深拷贝方式。Navigation 通过 pushPathByName 传递,NavDestination 的 onReady 中接收,引用传递性能更优。返回参数可通过 onPop 回调或 onResult 处理。
页面转场动画
Router 使用 pageTransition 配合 PageTransitionEnter / PageTransitionExit,支持 slide、translate、scale、opacity 等效果。Navigation 使用 customNavContentTransition 统一控制转场动画。
选型建议
单模块简单应用可用 Router;中大型应用推荐 Navigation + NavPathStack;跨模块跳转场景使用 Router。Navigation 具备更强的路由栈操作能力、更好的性能表现和更灵活的自定义能力。
下节预告
第6课将深入讲解 ArkUI 中的状态管理进阶,包括 @Observed 与 @ObjectLink 实现对象属性的深度监听、@Watch 监听状态变化、AppStorage 应用级状态管理以及 LocalStorage 页面级状态管理。