Router 路由管理:pushUrl、replaceUrl、back 与参数传递

前言

在移动应用中,页面路由 是连接不同功能模块的纽带。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.pushUrlparams 参数大小有限制(通常不超过 2KB)。对于大量数据,建议使用全局状态或持久化存储。


总结

本文通过"海风日记"的完整源码,详细讲解了 HarmonyOS Router 路由管理的完整体系:

  1. pushUrl:页面跳转,支持参数传递和模态跳转
  2. replaceUrl:页面替换,适用于登录、引导等场景
  3. router.back:返回上一页,支持参数回传
  4. 参数传递:类型安全、参数验证、复杂对象传递
  5. 路由模式:标准、替换、单例模式的对比和选择
  6. 生命周期联动:路由跳转时的生命周期执行顺序
  7. 最佳实践:页面栈管理、数据同步、参数验证

下一篇文章将深入讲解 BarMode.Fixed 与 scrollable 属性,分析 Tab 栏固定模式与滚动模式的选择策略和适配方案,敬请期待。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

相关推荐
wear工程师9 小时前
CompletableFuture 的 allOf 到底怎么收结果?面试别只说并行
java·后端
b130538100499 小时前
DiaryDetail2Page 卡片式详情:琥珀渐变 + 引号装饰
后端
w139548564229 小时前
AI 周报卡片:智能生成内容的 UI 呈现
后端
全堆鸿蒙9 小时前
天气药丸标签:Row + Text 组合实现品牌药丸
后端
HONG````10 小时前
HarmonyOS ArkUI Slider 与 Rating 实战:音量控制、亮度调节与星级评分
后端
hdsoft_huge10 小时前
SpringBoot系列18:SpringSecurity登录鉴权,JWT无状态Token认证完整流程(生产级落地)
java·spring boot·后端
Csvn10 小时前
📊 SQL 入门 Day 5:GROUP BY & HAVING — 开始分组分析
后端·sql
l1340620823510 小时前
空状态引导设计:Text + Button 组合与交互反馈
后端
2501_9185823710 小时前
月统计面板:日记数 + 连续天数 + 主要心情
后端