HarmonyOS 6.0 页面间数据传递与参数序列化
页面跳转带数据,这谁不会?pushPath的时候把参数传过去就完了。但现实远没这么简单——传个字符串没问题,传个对象呢?传个Date呢?传个100KB的JSON呢?跨Ability跳转呢?返回时怎么带数据回来?每个问题踩一遍,你就知道"传参"这两个字背后有多少坑。
Navigation参数传递

NavPathStack的pushPath支持param参数:
this.navPathStack.pushPath({
name: 'DetailPage',
param: { id: 42, title: '测试文章' }
});
目标页面通过onGetParam回调接收:
@Builder
DetailPage() {
NavDestination() {
DetailContent()
}
.onGetParam((param: Object) => {
let p = param as Record<string, Object>;
this.itemId = p['id'] as number;
this.itemTitle = p['title'] as string;
})
}
关键:param的类型是Object,需要手动转换。 ArkTS不支持泛型参数,所以pushPath时传入的param在接收端是Object类型,必须用as转换或逐字段取值。
参数类型限制
Navigation的param参数只支持可序列化类型。这意味着:
| 类型 | 能传吗 | 说明 |
|---|---|---|
| number/string/boolean | 能 | 基本类型没问题 |
| 普通对象(Record) | 能 | 字段都是基本类型 |
| 数组 | 能 | 元素是基本类型 |
| Date | 不能 | 反序列化后变成string |
| Map/Set | 不能 | 序列化后丢失类型 |
| 函数/回调 | 不能 | 不可序列化 |
| class实例 | 看情况 | 只有序列化友好的字段能保留 |
Date是高频踩坑点。你以为传了个Date对象过去,收到一打开发现是个ISO字符串。解决方式是传时间戳:
// 发送端
this.navPathStack.pushPath({
name: 'SchedulePage',
param: { timestamp: Date.now() }
});
// 接收端
let timestamp = p['timestamp'] as number;
let date = new Date(timestamp);
复杂对象传递
对象嵌套对象时,序列化会"展平"类型信息:
interface Address {
city: string;
street: string;
}
interface User {
name: string;
age: number;
address: Address;
}
// 传参
let user: User = {
name: '张三',
age: 25,
address: { city: '北京', street: '中关村' }
};
this.navPathStack.pushPath({ name: 'UserDetail', param: user });
// 接收端——address的类型信息丢失了
.onGetParam((param: Object) => {
let p = param as Record<string, Object>;
let name = p['name'] as string;
let addressObj = p['address'] as Record<string, Object>;
let city = addressObj['city'] as string;
})
嵌套层级越深,类型转换越痛苦。实际项目中建议只传ID,到目标页面再查数据:
// 只传ID
this.navPathStack.pushPath({ name: 'UserDetail', param: { userId: user.id } });
// 目标页面用ID查完整数据
.onGetParam(async (param: Object) => {
let p = param as Record<string, Object>;
let userId = p['userId'] as number;
this.user = await this.userService.getById(userId);
})
只传ID的好处:数据永远是新鲜的(不会因为传了旧数据而显示过期内容),参数体积小,不需要序列化复杂对象。
返回值传递
NavPathStack支持pop带返回值:
// 详情页返回时带数据
this.navPathStack.pop({ updated: true, itemId: 42 });
// 上一个页面接收
this.navPathStack.pushPath({
name: 'DetailPage',
param: { id: 42 }
}).then((result: Object) => {
let r = result as Record<string, Object>;
let updated = r['updated'] as boolean;
if (updated) {
this.refreshList();
}
});
注意:不是所有pushPath都支持then接收返回值。 只有当目标页面用pop(Object)返回时,then才会触发。如果用户点系统返回键或者手势返回,pop没带参数,then就不会触发。
更可靠的方式是监听navPathStack的变化:
aboutToAppear(): void {
this.navPathStack.on('pop', (info: PopInfo) => {
if (info.param !== undefined) {
let result = info.param as Record<string, Object>;
let updated = result['updated'] as boolean;
if (updated) {
this.refreshList();
}
}
});
}
on(‘pop’)在每次栈弹出时都会触发,不管是代码pop还是手势返回。比then更可靠。
大数据传递
参数体积过大(超过100KB)时,Navigation的序列化会有性能问题。几个解决方案:
- 全局状态:把数据放到AppStorage或PersistentStorage,只传key
// 写入全局状态
AppStorage.setOrCreate('draftArticle', article);
// 只传标记
this.navPathStack.pushPath({ name: 'Preview', param: { from: 'draft' } });
// 目标页读取
let article = AppStorage.get<Article>('draftArticle');
- 文件中转:数据写临时文件,传文件路径
import { fileIo } from '@kit.CoreFileKit';
// 写入临时文件
let tempPath = this.context.tempDir + '/transfer_data.json';
fileIo.writeTextSync(tempPath, JSON.stringify(largeData));
this.navPathStack.pushPath({ name: 'Target', param: { filePath: tempPath } });
// 目标页读取
let content = fileIo.readTextSync(filePath);
- 单例管理器:用内存中的单例持有数据
class DataTransferManager {
private static instance: DataTransferManager;
private cache: Map<string, Object> = new Map();
static getInstance(): DataTransferManager {
if (!DataTransferManager.instance) {
DataTransferManager.instance = new DataTransferManager();
}
return DataTransferManager.instance;
}
put(key: string, data: Object): void {
this.cache.set(key, data);
}
get(key: string): Object | undefined {
return this.cache.get(key);
}
remove(key: string): void {
this.cache.delete(key);
}
}
// 使用
let transferKey = 'data_' + Date.now().toString();
DataTransferManager.getInstance().put(transferKey, largeData);
this.navPathStack.pushPath({ name: 'Target', param: { transferKey: transferKey } });
// 目标页
let data = DataTransferManager.getInstance().get(transferKey);
DataTransferManager.getInstance().remove(transferKey);
单例方式最简单,但要记得用完删除,否则内存泄漏。
跨Ability传递
跨Ability跳转用want传参:
import { common, Want } from '@kit.AbilityKit';
let want: Want = {
bundleName: 'com.example.app',
abilityName: 'SecondAbility',
parameters: {
userId: 42,
userName: '张三'
}
};
this.context.startAbility(want);
目标Ability在onCreate/onWindowStageCreate中获取参数:
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
let params = want.parameters;
let userId = params['userId'] as number;
}
跨Ability的parameters限制更严格——只支持基本类型和简单对象,不支持函数、class实例、循环引用对象。这跟Navigation的param限制一样,但跨Ability序列化是系统级操作,成本更高。
参数校验
永远不要假设参数一定存在:
.onGetParam((param: Object) => {
if (param === null || param === undefined) {
// 无参数进入,显示默认内容
this.itemId = -1;
return;
}
let p = param as Record<string, Object>;
if (p['id'] !== undefined) {
this.itemId = p['id'] as number;
} else {
this.itemId = -1;
}
})
用户可能从深链接、通知、桌面快捷方式进入,这些场景可能没有参数。不校验就crash。
参数与深链接
deepLink进入时参数通过want传递,格式是URI:
// scheme://host/path?key=value
let want: Want = {
uri: 'myapp://detail?id=42&from=notification'
};
// 在Ability中解析
let uri = want.uri as string;
let url = new URL(uri);
let id = url.searchParams.get('id');
URL解析支持查询参数。但注意:URI里的参数都是字符串类型,"42"需要手动parseInt转数字。
踩坑清单
| 问题 | 原因 | 解决 |
|---|---|---|
| 传Date收到字符串 | Date被序列化为ISO字符串 | 传时间戳number |
| 传class实例丢失方法 | 序列化只保留数据字段 | 只传数据,方法在目标页重建 |
| 返回值then不触发 | 用户手势返回没带参数 | 用on(‘pop’)监听 |
| 大对象传递卡顿 | Navigation序列化开销 | 用AppStorage或单例中转 |
| 跨Ability参数丢失 | parameters不支持复杂类型 | 只传基本类型或JSON字符串 |
| 参数undefined导致crash | 没做空值校验 | 每个字段都判空 |
| 深链接参数是字符串 | URI查询参数无类型 | 手动parseInt/parseFloat |
| Map/Set传过去变空 | 不可序列化 | 转成数组再传 |
| 嵌套对象类型丢失 | 反序列化返回Record | 只传ID或逐层as转换 |
| 临时文件没清理 | tempDir写入后忘记删 | aboutToDisappear中清理 |
传参的核心原则:简单数据用Navigation param,复杂数据用全局状态或单例中转,跨Ability用want parameters,永远只传最小必要信息。 传ID不传对象,传时间戳不传Date,传key不传数据。这不是偷懒,是工程素养。
更多推荐



所有评论(0)