
前言
在移动应用中,页面路由 是连接不同功能模块的纽带。HarmonyOS 提供了 @ohos.router 模块,支持页面跳转、参数传递、路由栈管理等功能。
"海风日记"全项目共有 28 个页面,涉及大量路由跳转场景:从闪屏到首页、从首页到详情、从详情到编辑、从设置页面跳转到子页面等。本文将从源码出发,全面讲解 Router 路由管理的各项 API 和实战用法。
一、Router 模块概述
1.1 引入 Router 模块
在 HarmonyOS 中,所有页面路由操作通过 @ohos.router 模块实现:
typescript
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 中注册:
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() 将新页面压入栈顶,保留当前页面:
typescript
// 跳转到写日记页
Button() {
SymbolGlyph($r('sys.symbol.plus'))
}
.onClick(() => {
router.pushUrl({ url: 'pages/diary/WriteDiaryPage' })
})
2.2 带参数跳转
使用 params 字段传递参数:
typescript
// 跳转并传递参数
router.pushUrl({
url: 'pages/diary/DiaryDetailPage',
params: { diaryId: item.id }
})
2.3 接收参数
目标页面通过 router.getParams() 接收参数:
typescript
@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.pushUrl params |
简单直接 | 参数大小有限制 | 普通参数传递 |
| 全局状态共享 | 跨页面共享 | 需要额外状态管理 | 全局用户信息 |
| 持久化存储 | 数据持久化 | 读写开销大 | 需要持久化的数据 |
2.5 pushUrl 的完整参数
typescript
interface RouterOptions {
url: string // 目标页面路径
params?: Object // 传递的参数
modal?: boolean // 是否以模态方式打开
}
// 模态跳转(新页面从底部滑入)
router.pushUrl({
url: 'pages/diary/WriteDiaryPage',
modal: true
})
三、replaceUrl:页面替换
3.1 基本用法
router.replaceUrl() 替换当前页面,当前页面会被销毁:
typescript
// 登录成功后替换到首页
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 的选择
typescript
// 需要保留返回路径时使用 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 基本用法
typescript
// 返回上一页
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() 支持传递参数给上一页:
typescript
// 返回并传递数据
router.back({
url: 'pages/diary/DiaryDetailPage',
params: { updated: true }
})
4.3 返回指定页面
如果要从深层页面返回到特定页面,可以通过多次 back() 或使用 router.clear():
typescript
// 返回到指定页面(需要知道页面路径)
router.back({ url: 'pages/Index' })
4.4 返回前的确认
在写日记页面,如果用户有未保存的内容,可以在返回前确认:
typescript
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 传递复杂对象
typescript
// 传递复杂对象
router.pushUrl({
url: 'pages/diary/DiaryDetailPage',
params: {
diaryId: '1',
source: 'calendar',
readOnly: true
}
})
5.2 接收参数的类型安全
推荐使用类型断言确保参数安全:
typescript
interface DiaryDetailParams {
diaryId: string
source?: string
readOnly?: boolean
}
aboutToAppear() {
const params = router.getParams() as DiaryDetailParams
if (params?.diaryId) {
this.diaryId = params.diaryId
}
}
5.3 参数验证
在实际项目中,建议对参数进行验证:
typescript
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)
typescript
// 标准跳转:新页面压入栈顶
router.pushUrl({
url: 'pages/diary/DiaryDetailPage',
params: { diaryId: '1' }
})
6.2 替换模式(replaceUrl)
typescript
// 替换跳转:替换当前页面
router.replaceUrl({
url: 'pages/Index'
})
6.3 单例模式
如果希望某个页面在栈中只有一个实例,可以在跳转前检查:
typescript
// 检查页面是否已在栈中
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 利用生命周期管理数据
typescript
@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() 中获取参数:
typescript
aboutToAppear() {
// 此时参数已可用
const params = router.getParams() as Record<string, string>
}
8.2 页面栈溢出
问题:长时间使用后页面跳转异常。
原因:页面栈中积累了大量页面,超过了栈的最大容量(默认 32 个)。
解决方案:
typescript
// 定期清理页面栈
if (router.getLength() > 20) {
// 清空栈后重新跳转
router.clear()
router.pushUrl({ url: 'pages/Index' })
}
8.3 跨页面数据同步
问题:页面 A 修改了数据,返回页面 B 后数据未更新。
解决方案 :在 onPageShow() 中刷新数据:
typescript
@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 栏固定模式与滚动模式的选择策略和适配方案,敬请期待。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源: