《鸿蒙原生应用开发实战》第四篇:多页面导航与参数传递实战
《鸿蒙原生应用开发实战》第四篇:多页面导航与参数传递实战
前言
一个真正的应用不可能只有一个页面。页面之间的跳转、参数传递、数据回传是开发中频率最高的操作。HarmonyOS 提供了强大的 router 路由模块来管理页面导航。
在本篇中,我们将结合「光遇·心境」应用的 5 个页面,深入讲解:
- router 路由核心 API
- 页面间参数传递的多种方式
- 页面生命周期与数据加载时机
- 路由栈管理与返回处理
- 参数类型安全与错误处理
一、路由系统概述
路由表配置
所有页面必须在 main_pages.json 中注册:
{
"src": [
"pages/Index",
"pages/ScenePage",
"pages/DetailPage",
"pages/FavPage",
"pages/ProfilePage"
]
}
注册时只写路径(不含 .ets 后缀),路径相对于 ets/ 目录。5 个页面组成了一个完整的导航体系:
┌─────────────┐
│ Index │ ← 首页(入口)
└──────┬──────┘
┌────────────────┼────────────────┐
▼ ▼ ▼
┌──────────┐ ┌───────────┐ ┌───────────┐
│ ScenePage│ │ FavPage │ │ProfilePage│
└─────┬────┘ └───────────┘ └───────────┘
▼
┌───────────┐
│DetailPage │ ← 详情页(可来自多个入口)
└───────────┘
二、router 核心 API
导入方式
API 23 强制要求从 @ohos.router 导入:
// ✅ 正确 — API 23 支持的方式
import router from '@ohos.router';
// ❌ 错误 — API 23 不导出此路径
// import { router } from '@kit.AbilityKit';
页面跳转
// 基本跳转(无参数)
router.pushUrl({ url: 'pages/FavPage' });
// 带参数跳转
router.pushUrl({
url: 'pages/DetailPage',
params: { sceneId: 1 }
});
// 多个参数
router.pushUrl({
url: 'pages/ScenePage',
params: {
category: '海洋',
from: 'homepage'
}
});
页面返回
// 简单返回上一页
router.back();
// 返回并携带数据回传
router.back({
url: 'pages/Index',
params: { result: 'fromDetail' }
});
获取参数
// 在目标页面的 aboutToAppear 中获取参数
aboutToAppear(): void {
const params = router.getParams() as Record<string, Object>;
if (params && params['sceneId']) {
const id = params['sceneId'] as number;
this.scene = getSceneById(id);
}
}
三、参数传递实战案例
案例1:首页 → 场景列表(分类筛选)
首页的分类入口点击后,将分类名称传递给 ScenePage:
// Index.ets — 分类入口点击
CategoryItem(name: string) {
Column() {
Text(this.categoryIcons[name] || '✨').fontSize(28)
Text(name).fontSize($r('app.float.caption_font_size'))
}
.onClick(() => {
router.pushUrl({
url: 'pages/ScenePage',
params: { category: name } // 传递"晨光"/"森林"等
});
})
}
在 ScenePage 接收并使用参数:
// ScenePage.ets
@State selectedCategory: string = '全部';
@State scenes: SceneItem[] = getScenes();
aboutToAppear(): void {
const params = router.getParams() as Record<string, Object>;
if (params && params['category']) {
const cat = params['category'] as string;
this.selectedCategory = cat;
this.scenes = getScenesByCategory(cat); // 根据分类筛选
}
}
效果:从首页点击"海洋"分类 → 直接跳转到 ScenePage 并展示所有海洋场景。
案例2:多个入口 → 详情页
DetailPage 可以从 3 个不同入口进入:
// 入口1:首页的每日推荐卡片
// Index.ets
router.pushUrl({
url: 'pages/DetailPage',
params: { sceneId: this.dailyScene.id }
});
// 入口2:场景列表的卡片
// ScenePage.ets
router.pushUrl({
url: 'pages/DetailPage',
params: { sceneId: item.id }
});
// 入口3:收藏列表的卡片
// FavPage.ets
router.pushUrl({
url: 'pages/DetailPage',
params: { sceneId: item.id }
});
详情页统一接收:
// DetailPage.ets
@State scene: SceneItem | undefined = undefined;
aboutToAppear(): void {
const params = router.getParams() as Record<string, Object>;
if (params && params['sceneId']) {
const id = params['sceneId'] as number;
this.scene = getSceneById(id);
if (this.scene) {
this.checkFavStatus();
}
}
}
设计原则:DetailPage 不关心从哪个入口来的,它只读取
sceneId参数。这种松耦合的设计让代码更容易维护。
四、页面生命周期与数据加载
页面生命周期方法
| 方法 | 触发时机 | 适用场景 |
|---|---|---|
aboutToAppear() |
页面即将显示(每次进入) | 加载数据、读取参数 |
onPageShow() |
页面每次显示时 | 刷新数据(从其他页面返回时) |
onPageHide() |
页面隐藏时 | 暂停动画/音视频 |
aboutToDisappear() |
页面销毁前 | 释放资源 |
aboutToAppear vs onPageShow 的区别
// 场景:收藏页 → 详情页 → 点击收藏 → 返回收藏页
// aboutToAppear() — 只在新创建页面时调用
// 第一次进入 FavPage: ✅ 调用
// 从 DetailPage 返回 FavPage: ❌ 不调用(页面还在栈中)
// onPageShow() — 每次页面显示时都调用
// 第一次进入 FavPage: ✅ 调用
// 从 DetailPage 返回 FavPage: ✅ 调用
这就是为什么我们在 FavPage 中使用 aboutToAppear 而不是 onPageShow —— 因为收藏列表在返回时需要自动刷新,我们需要在每次显示时重新加载数据:
@Component
struct FavPage {
@State favScenes: SceneItem[] = [];
aboutToAppear(): void {
this.loadFavScenes(); // 每次进入页面时刷新收藏列表
}
loadFavScenes(): void {
const favIds: number[] = AppStorage.get<number[]>(FAV_KEY) || [];
const list: SceneItem[] = [];
for (const id of favIds) {
const scene = getSceneById(id);
if (scene) {
list.push(scene);
}
}
this.favScenes = list;
this.favCount = list.length;
}
}
性能建议
aboutToAppear中不要做耗时操作(如网络请求),如果需要异步加载数据,使用aboutToAppear发起请求,用@State监听结果- 频繁刷新的数据用
onPageShow,一次性初始化用aboutToAppear
五、路由栈管理
路由栈的工作机制
router 内部维护了一个页面栈(LIFO):
初始状态: [Index]
push FavPage: [Index, FavPage]
push DetailPage: [Index, FavPage, DetailPage]
back(): [Index, FavPage] ← DetailPage 出栈
back(): [Index] ← FavPage 出栈
router.pushUrl 与 router.replaceUrl
| API | 行为 | 路由栈 |
|---|---|---|
pushUrl |
压入新页面,保留当前页面 | 栈增长 |
replaceUrl |
替换当前页面,不保留 | 栈不变 |
back() |
弹出栈顶页面 | 栈缩小 |
clear() |
清空栈 | 空栈 |
replaceUrl 的典型使用场景是登录页 → 首页:
// 登录成功后替换,用户无法返回到登录页
router.replaceUrl({ url: 'pages/Index' });
避免路由栈过深
理论上路由栈可以无限增长,但过深的栈会消耗内存。建议:
- 不用的页面及时
back() - 使用
replaceUrl替代某些场景的pushUrl - 监听页面栈数量
六、参数传递的高级话题
参数类型安全
由于 router.getParams() 返回的是 Object 类型,需要进行类型断言:
// 安全获取参数的模式
aboutToAppear(): void {
const params = router.getParams() as Record<string, Object> | undefined;
// 使用可选链和类型守卫
const sceneId = params?.['sceneId'];
if (typeof sceneId === 'number') {
this.scene = getSceneById(sceneId);
}
// 或者使用断言 + 兜底
const cat = (params?.['category'] as string) || '全部';
this.selectedCategory = cat;
}
可以传递的数据类型
| 类型 | 示例 | 是否支持 |
|---|---|---|
| string | { name: 'hello' } |
✅ |
| number | { id: 42 } |
✅ |
| boolean | { isAdmin: true } |
✅ |
| Object | { user: { name: 'Tom' } } |
✅ |
| Array | { ids: [1,2,3] } |
✅ |
| Function | { cb: ()=>{} } |
❌ 不支持 |
| 复杂嵌套 | 对象含多层嵌套 | ✅ 会被序列化 |
参数的大小限制
路由参数不要传大数据(超过 10KB 的数据建议用 AppStorage 或全局变量共享),因为参数在序列化/反序列化过程中有性能开销。
七、完整的导航流程图
┌─────────────────────────────────────────────────────────────┐
│ 用户操作流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 打开App → Index(首页) │
│ ├── 点击"每日推荐" → push({sceneId}) → DetailPage │
│ │ └── 点击收藏 ❤️ → AppStorage 更新 → back │
│ │ │
│ ├── 点击"晨光"分类 → push({category: '晨光'}) → ScenePage│
│ │ └── 点击场景卡片 → push({sceneId}) → DetailPage │
│ │ └── back → back │
│ │ │
│ ├── 点击❤️按钮 → push({}) → FavPage │
│ │ ├── 点击卡片 → push({sceneId}) → DetailPage │
│ │ └── 点击取消收藏 → AppStorage 更新 │
│ │ │
│ └── 点击👤按钮 → push({}) → ProfilePage │
│ └── back │
│ │
│ 任意页面 ← back 返回上一页 │
│ │
└─────────────────────────────────────────────────────────────┘
八、常见踩坑记录
坑1:使用 @kit.AbilityKit 的 router
现象:编译通过但运行时 crash
原因:API 23 不导出 @kit.AbilityKit 中的 router
解决:统一使用 import router from '@ohos.router'
坑2:参数接收不到
现象:router.getParams() 返回 undefined
原因:在 aboutToAppear 之前调用,或页面未通过 router 进入
解决:在 aboutToAppear 生命周期中获取参数
坑3:返回时数据没有刷新
现象:从详情页取消收藏返回收藏页,列表没有变化
原因:FavPage 在 aboutToAppear 中加载数据,但页面在栈中未被销毁,不会调用 aboutToAppear
解决:使用 onPageShow() 代替,或在 AppStorage 上注册监听
九、路由与状态管理的协作模式
// 推荐的协作模式
// 1. 路由负责页面导航和轻量参数
router.pushUrl({
url: 'pages/DetailPage',
params: { sceneId: 1 } // 只传递 ID,不传递完整数据
});
// 2. 共享数据通过 AppStorage 管理
AppStorage.set<number[]>(FAV_KEY, favList);
// 3. 页面从数据仓库获取完整数据
const scene = getSceneById(sceneId); // 通过 ID 获取
这种设计的好处:
- 参数简洁,只传 ID 不传整个对象
- 数据源统一,避免多页面数据不一致
- 调试方便,数据流向清晰

总结
本篇我们深入学习了:
- ✅ router 路由核心 API 与导入规范
- ✅ 4 种参数传递实战场景
- ✅ 页面生命周期与数据加载时机
- ✅ 路由栈管理与返回机制
- ✅ 参数类型安全与错误处理
- ✅ 路由与状态管理的协作模式
路由是连接页面的桥梁,掌握好这一章,多页面应用开发就会得心应手。最后一篇将聚焦完整的收藏功能闭环与应用发布准备。
下一篇预告:收藏功能、资源管理与构建发布 —— 从功能闭环到 HAP 包发布全流程
更多推荐




所有评论(0)