Router 路由管理:pushUrl、replaceUrl、back 与参数传递
前言
在移动应用中,页面路由是连接不同功能模块的纽带。HarmonyOS 提供了@ohos.router模块,支持页面跳转、参数传递、路由栈管理等功能。
“海风日记“全项目共有28 个页面,涉及大量路由跳转场景:从闪屏到首页、从首页到详情、从详情到编辑、从设置页面跳转到子页面等。本文将从源码出发,全面讲解 Router 路由管理的各项 API 和实战用法。
一、Router 模块概述
1.1 引入 Router 模块
在 HarmonyOS 中,所有页面路由操作通过@ohos.router模块实现:
import router from '@ohos.router'1.2 Router 的核心 API
| API | 功能 | 页面栈影响 | 是否保留当前页 |
|---|---|---|---|
router.pushUrl() | 跳转到新页面 | 压栈 | 保留 |
router.replaceUrl() | 替换当前页面 | 替换栈顶 | 销毁 |
router.back() | 返回上一页 | 出栈 | 销毁当前页 |
router.clear() | 清空页面栈 | 清空 | 全部销毁 |
router.getLength() | 获取栈中页面数 | 无影响 | - |
router.getState() | 获取当前页面状态 | 无影响 | - |
1.3 路由注册
所有页面必须在main_pages.json中注册:
{ "src": [ "pages/SplashPage", "pages/Index", "pages/onboarding/OnboardingPage", "pages/login/LoginPhonePage", "pages/login/LoginNamePage", "pages/diary/WriteDiaryPage", "pages/diary/EditDiaryPage", "pages/diary/DiaryDetailPage", "pages/diary/DiaryDetail2Page", "pages/calendar/CalendarYearPage", "pages/search/SearchPage", "pages/tag/TagDetailPage", "pages/premium/PremiumPage", "pages/privacy/PrivacyPasswordPage", "pages/image/ImageViewerPage", "pages/mine/ProfileInfoPage", "pages/mine/PersonalSettingsPage", "pages/mine/AccountSettingsPage", "pages/mine/PhoneBindPage", "pages/mine/TagManagePage", "pages/mine/SpaceManagePage", "pages/mine/DataManagePage", "pages/mine/FaqPage", "pages/mine/AboutPage", "pages/mine/EmailFeedbackPage", "pages/mine/DarkModePage" ] }二、pushUrl:页面跳转
2.1 基本用法
router.pushUrl()将新页面压入栈顶,保留当前页面:
// 跳转到写日记页 Button() { SymbolGlyph($r('sys.symbol.plus')) } .onClick(() => { router.pushUrl({ url: 'pages/diary/WriteDiaryPage' }) })2.2 带参数跳转
使用params字段传递参数:
// 跳转并传递参数 router.pushUrl({ url: 'pages/diary/DiaryDetailPage', params: { diaryId: item.id } })2.3 接收参数
目标页面通过router.getParams()接收参数:
@Entry @Component struct DiaryDetailPage { private diary = MOCK_DIARIES[0] aboutToAppear() { const params = router.getParams() as Record<string, string> if (params && params.diaryId) { const found = MOCK_DIARIES.find(d => d.id === params.diaryId) if (found) this.diary = found } } }2.4 参数传递的方式对比
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
router.pushUrlparams | 简单直接 | 参数大小有限制 | 普通参数传递 |
| 全局状态共享 | 跨页面共享 | 需要额外状态管理 | 全局用户信息 |
| 持久化存储 | 数据持久化 | 读写开销大 | 需要持久化的数据 |
2.5 pushUrl 的完整参数
interface RouterOptions { url: string // 目标页面路径 params?: Object // 传递的参数 modal?: boolean // 是否以模态方式打开 } // 模态跳转(新页面从底部滑入) router.pushUrl({ url: 'pages/diary/WriteDiaryPage', modal: true })三、replaceUrl:页面替换
3.1 基本用法
router.replaceUrl()替换当前页面,当前页面会被销毁:
// 登录成功后替换到首页 Button('开始写日记 🌊') .onClick(() => { router.replaceUrl({ url: 'pages/onboarding/OnboardingPage' }) })3.2 典型应用场景
在“海风日记“中,replaceUrl用于以下场景:
| 场景 | 源码 | 说明 |
|---|---|---|
| 闪屏 → 登录 | SplashPage.ets | 点击登录按钮,替换闪屏 |
| 闪屏 → 首页 | SplashPage.ets | 点击“直接体验“,跳过登录和引导 |
| 登录 → 引导 | LoginNamePage.ets | 昵称登录完成后进入引导页 |
| 引导 → 首页 | OnboardingPage.ets | 引导结束进入首页 |
| 退出登录 → 闪屏 | MineTab.ets | 退出登录回到闪屏 |
3.3 pushUrl vs replaceUrl 的选择
// 需要保留返回路径时使用 pushUrl router.pushUrl({ url: 'pages/diary/DiaryDetailPage' }) // 用户可以从详情页返回到首页 // 不需要保留当前页时使用 replaceUrl router.replaceUrl({ url: 'pages/Index' }) // 用户从闪屏进入首页后,无法返回到闪屏3.4 页面栈的完整流程
“海风日记“应用启动的页面栈变化:
步骤 1:闪屏页 [SplashPage] 步骤 2:点击"登录"(replaceUrl) [LoginPhonePage] 步骤 3:登录成功(replaceUrl) [LoginNamePage] 步骤 4:昵称设置(replaceUrl) [OnboardingPage] 步骤 5:引导结束(replaceUrl) [Index] 步骤 6:点击日记(pushUrl) [Index, DiaryDetailPage] 步骤 7:点击编辑(pushUrl) [Index, DiaryDetailPage, EditDiaryPage] 步骤 8:保存返回(back) [Index, DiaryDetailPage] 步骤 9:返回首页(back) [Index]四、router.back:返回上一页
4.1 基本用法
// 返回上一页 router.back() // 在导航栏关闭按钮中使用 Button() { SymbolGlyph($r('sys.symbol.xmark')) .fontSize(18).fontColor([COLOR_TEXT_MAIN]) } .backgroundColor('transparent').width(44).height(44) .onClick(() => router.back())4.2 带参数返回
router.back()支持传递参数给上一页:
// 返回并传递数据 router.back({ url: 'pages/diary/DiaryDetailPage', params: { updated: true } })4.3 返回指定页面
如果要从深层页面返回到特定页面,可以通过多次back()或使用router.clear():
// 返回到指定页面(需要知道页面路径) router.back({ url: 'pages/Index' })4.4 返回前的确认
在写日记页面,如果用户有未保存的内容,可以在返回前确认:
Button() { SymbolGlyph($r('sys.symbol.xmark')) } .onClick(() => { if (this.diaryText.length > 0) { // 弹窗确认是否放弃 AlertDialog.show({ title: '放弃编辑?', message: '当前内容尚未保存', primaryButton: { value: '继续编辑', action: () => {} }, secondaryButton: { value: '放弃', action: () => { router.back() } } }) } else { router.back() } })五、路由参数传递详解
5.1 传递复杂对象
// 传递复杂对象 router.pushUrl({ url: 'pages/diary/DiaryDetailPage', params: { diaryId: '1', source: 'calendar', readOnly: true } })5.2 接收参数的类型安全
推荐使用类型断言确保参数安全:
interface DiaryDetailParams { diaryId: string source?: string readOnly?: boolean } aboutToAppear() { const params = router.getParams() as DiaryDetailParams if (params?.diaryId) { this.diaryId = params.diaryId } }5.3 参数验证
在实际项目中,建议对参数进行验证:
aboutToAppear() { const params = router.getParams() as Record<string, string> if (!params) { hilog.error(DOMAIN, TAG, '未收到路由参数') router.back() return } if (!params.diaryId) { hilog.error(DOMAIN, TAG, '缺少 diaryId 参数') router.back() return } // 参数有效,加载数据 this.loadDiary(params.diaryId) }六、路由模式详解
6.1 标准模式(pushUrl)
// 标准跳转:新页面压入栈顶 router.pushUrl({ url: 'pages/diary/DiaryDetailPage', params: { diaryId: '1' } })6.2 替换模式(replaceUrl)
// 替换跳转:替换当前页面 router.replaceUrl({ url: 'pages/Index' })6.3 单例模式
如果希望某个页面在栈中只有一个实例,可以在跳转前检查:
// 检查页面是否已在栈中 const state = router.getState() if (state.name !== 'DiaryDetailPage') { router.pushUrl({ url: 'pages/diary/DiaryDetailPage' }) }6.4 模式对比
| 模式 | API | 页面栈行为 | 返回行为 |
|---|---|---|---|
| 标准模式 | pushUrl | 新页面压栈 | 可返回 |
| 替换模式 | replaceUrl | 替换栈顶 | 不可返回 |
| 清空模式 | clear+pushUrl | 清空栈再压栈 | 不可返回 |
| 返回模式 | back | 弹出栈顶 | 到上一页 |
七、页面间的生命周期联动
7.1 路由跳转的生命周期顺序
从页面 A 跳转到页面 B 时,生命周期的执行顺序:
1. 页面 A: aboutToDisappear() 2. 页面 B: aboutToAppear() 3. 页面 B: build() 执行 4. 页面 A: onPageHide() 5. 页面 B: onPageShow()7.2 返回时的生命周期
从页面 B 返回页面 A 时:
1. 页面 B: aboutToDisappear() 2. 页面 A: aboutToAppear() 3. 页面 A: build() 重新执行 4. 页面 B: onPageHide() 5. 页面 A: onPageShow()7.3 利用生命周期管理数据
@Entry @Component struct DiaryDetailPage { @State diary: DiaryEntry aboutToAppear() { // 每次进入页面时刷新数据 this.loadDiaryData() } onPageShow() { // 从其他页面返回时刷新 this.loadDiaryData() } loadDiaryData() { const params = router.getParams() as Record<string, string> if (params?.diaryId) { this.diary = MOCK_DIARIES.find(d => d.id === params.diaryId) } } }八、常见问题与最佳实践
8.1 路由参数未收到
问题:目标页面router.getParams()返回undefined。
原因:在build()方法中调用getParams()时,参数可能尚未传递完成。
解决方案:在aboutToAppear()中获取参数:
aboutToAppear() { // 此时参数已可用 const params = router.getParams() as Record<string, string> }8.2 页面栈溢出
问题:长时间使用后页面跳转异常。
原因:页面栈中积累了大量页面,超过了栈的最大容量(默认 32 个)。
解决方案:
// 定期清理页面栈 if (router.getLength() > 20) { // 清空栈后重新跳转 router.clear() router.pushUrl({ url: 'pages/Index' }) }8.3 跨页面数据同步
问题:页面 A 修改了数据,返回页面 B 后数据未更新。
解决方案:在onPageShow()中刷新数据:
@Entry @Component struct HomeTab { @State diaryList: DiaryEntry[] = MOCK_DIARIES onPageShow() { // 每次页面显示时刷新数据 this.diaryList = [...MOCK_DIARIES] } }8.4 路由参数的大小限制
router.pushUrl的params参数大小有限制(通常不超过 2KB)。对于大量数据,建议使用全局状态或持久化存储。
总结
本文通过“海风日记“的完整源码,详细讲解了 HarmonyOS Router 路由管理的完整体系:
- pushUrl:页面跳转,支持参数传递和模态跳转
- replaceUrl:页面替换,适用于登录、引导等场景
- router.back:返回上一页,支持参数回传
- 参数传递:类型安全、参数验证、复杂对象传递
- 路由模式:标准、替换、单例模式的对比和选择
- 生命周期联动:路由跳转时的生命周期执行顺序
- 最佳实践:页面栈管理、数据同步、参数验证
下一篇文章将深入讲解BarMode.Fixed 与 scrollable 属性,分析 Tab 栏固定模式与滚动模式的选择策略和适配方案,敬请期待。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- @ohos.router API 文档
- HarmonyOS 页面路由开发指南
- 页面生命周期文档
- 海风日记项目源码
- [HarmonyOS 开发者官网](https://atomgit.com/openharmony/docs
- 开源鸿蒙跨平台社区
- ArkUI 导航组件
- UIAbility 生命周期