
页面跳转是应用开发中最基本的能力。在 HarmonyOS NEXT 中,router.pushPath 和 router.popPath 的组合经常被误用------很多人发现官方示例能运行,但实际项目里参数接收不到、返回后页面空白、状态丢失等问题频发。
这篇文章不讲概念,直接写一个完整的多页面跳转案例:A 页面点击按钮,跳转到 B 页面并传递一个用户 ID,B 页面接收并显示。代码全部可运行,同时把几个常见的坑和最佳实践说清楚。
它解决什么问题
HarmonyOS 的页面路由(@ohos.router)负责管理应用内页面的跳转与返回。它主要解决两个场景:
- 页面跳转:从一个页面导航到另一个页面,同时携带参数。
- 页面返回 :执行返回操作时,可以携带数据回传(通过
popPath的params)。
对比旧版本的 router.push(已废弃),router.pushPath 的优势在于:
- 支持更好的类型安全(官方推荐)
- 支持
recovery(页面栈恢复) - 配合
@Entry的生命周期更清晰
| 对比点 | router.pushPath |
router.push(旧) |
|---|---|---|
| 参数传递 | 使用 params 对象 |
使用 params 对象 |
| 页面栈恢复 | 支持 | 不支持 |
| 类型安全 | 强类型推荐 | 弱类型 |
| 生命周期 | 正常触发 | 正常触发 |
如果只是简单跳转,pushPath 足够。如果需要复杂的页面栈管理(如重定向、替换页面),可以用 pushNamedRoute 或 replacePath。
环境说明
text
DevEco Studio 版本:DevEco Studio 6.1.0 及以上
HarmonyOS SDK 版本:HarmonyOS 6.1.0(23) 及以上
目标设备:手机
核心实现
1. 页面注册(main_pages.json)
在项目的 resources/base/profile/main_pages.json 中注册两个页面:
json
{
"src": [
"pages/Index",
"pages/UserDetail"
]
}
如果不注册,跳转时会直接报错:Page not found。
2. A 页面(Index.ets)
A 页面有一个按钮,点击后跳转到 B 页面,并携带 userId 参数。
typescript
// pages/Index.ets
import { router } from '@kit.ArkUI'
@Entry
@Component
struct Index {
build() {
Column() {
Button('跳转到用户详情')
.onClick(() => {
router.pushPath({
url: 'pages/UserDetail',
params: {
userId: '12345'
}
})
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
注意 :params 是 Record<string, Object>,可以传递任意类型,但建议只传递基本类型或 JSON 序列化的对象。如果传递函数或日期对象,在 B 页面接收时可能丢失类型。
3. B 页面(UserDetail.ets)
B 页面接收参数并显示在界面上。
typescript
// pages/UserDetail.ets
import { router } from '@kit.ArkUI'
@Entry
@Component
struct UserDetail {
@State userId: string = ''
aboutToAppear(): void {
// 从路由参数中获取 userId
const params = router.getParams() as Record<string, string>
if (params && params.userId) {
this.userId = params.userId
}
}
build() {
Column() {
Text(`接收到的用户ID:${this.userId}`)
.fontSize(20)
.margin({ bottom: 20 })
Button('返回')
.onClick(() => {
router.popPath()
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
关键点:
- 参数接收必须在
aboutToAppear中执行(或onPageShow),不能在build()中获取,因为build()会多次调用,而参数只在页面初始化时有效。 - 使用
as Record<string, string>做类型断言,确保类型安全。但注意getParams()返回的是Object,如果参数是数字类型,可能会被转成字符串,具体取决于序列化方式。
常见问题与踩坑
问题 1:参数接收不到或者类型不对
现象 :B 页面 router.getParams() 返回 undefined 或者参数值是 [object Object]。
原因:
- 页面未在
main_pages.json注册,导致实际跳转失败。 params传递了非可序列化类型(如函数、Symbol),或嵌套过深的对象。- 在
build()中读取参数,但build()执行时参数还未加载完成。
解法:
- 检查
main_pages.json是否包含目标页面。 - 传递的参数保持简单,推荐用基本类型或扁平对象。
- 在
aboutToAppear生命周期内读取参数,不要推迟。
问题 2:返回后 A 页面状态丢失
现象 :从 B 页面通过 popPath 返回后,A 页面之前的状态(如输入框内容、滚动位置)恢复了默认值。
原因 :popPath 默认会销毁当前页面实例,但 A 页面本身属于上一个页面栈,它的状态由框架自动恢复------但仅限于通过 @State 或 @Prop 管理的属性。如果状态没有正确声明为响应式,就会丢失。
解法:
- 确保 A 页面中需要保持的状态都用
@State装饰。 - 如果 A 页面是
@Entry组件,它的状态在返回后会自动保留(因为页面栈未销毁),但前提是页面没有被系统回收(低内存时可能销毁)。
最佳实践
-
避免硬编码 URL
建议将页面路径定义为常量,例如
const PAGE_USER_DETAIL = 'pages/UserDetail'。否则后期重构时,一旦修改文件名,所有硬编码的跳转都会失效。 -
用
aboutToAppear接收参数这是页面初始化时最早的可控生命周期,保证参数在 UI 渲染前已正确赋值。不要在
build()中依赖参数,因为build()可能被多次触发。 -
返回时回传数据
如果需要从 B 页面向 A 页面回传数据,使用
router.popPath({ params: { result: 'ok' } })。A 页面可以通过onPageShow监听页面显示事件并获取回传参数。 -
页面栈深度控制
连续多次
pushPath会不断增加页面栈,可能导致内存问题。如果只需要覆盖当前页面,使用router.replacePath。
Demo 入口
完整的项目结构:
entry/src/main/ets/
├── pages/
│ ├── Index.ets // A 页面
│ └── UserDetail.ets // B 页面
├── resources/
│ └── base/
│ └── profile/
│ └── main_pages.json // 页面注册
Index.ets 和 UserDetail.ets 的完整代码已在上文给出。
FAQ
Q1:为什么在模拟器上跳转正常,真机上却报错"页面不存在"?
A:模拟器会自动同步 main_pages.json,但真机调试时可能存在缓存未更新。请手动执行"清理项目"或卸载重装应用。另外,检查 url 路径是否与 main_pages.json 中定义的 src 完全一致(包括大小写)。
Q2:为什么 router.getParams() 返回的对象中的数字变成了字符串?
A:目前 HarmonyOS 的路由参数序列化会将所有基本类型转为字符串,这是已知的限制。推荐在传递数字时,在接收端手动 parseInt 或 Number() 转换。
Q3:页面返回后,原来 A 页面的异步请求还在执行,会导致状态错误?
A:是的。在 aboutToDisappear(页面即将销毁)中应当取消所有异步任务(如网络请求、定时器)。特别是使用了 async/await 的地方,绑定 AbortController 或清理 setTimeout。
如果你也遇到类似的页面路由问题,可以重点检查生命周期和参数类型 ------ 这两处是初学者最容易出错的地方。