文章目录
-
- 一、三个组件标识属性的定位
- 二、属性签名与参数说明
- [三、为什么需要 restoreId?](#三、为什么需要 restoreId?)
- [四、前置配置:module.json5 开启状态恢复](#四、前置配置:module.json5 开启状态恢复)
- [五、示例代码解析(对应 index.ets RestoreIdExample)](#五、示例代码解析(对应 index.ets RestoreIdExample))
-
- [5.1 完整源码](#5.1 完整源码)
- [5.2 标注解析](#5.2 标注解析)
- 六、唯一性规则与常见错误
-
- [6.1 同页面内必须唯一](#6.1 同页面内必须唯一)
- [6.2 ForEach 动态渲染多个可滚动组件](#6.2 ForEach 动态渲染多个可滚动组件)
- [6.3 对非滚动组件设置(无效但不报错)](#6.3 对非滚动组件设置(无效但不报错))
- [七、与 .id() 联合使用](#七、与 .id() 联合使用)
- [八、与 .id() / .key() 的完整三属性对比](#八、与 .id() / .key() 的完整三属性对比)
- 十、总结
一、三个组件标识属性的定位
ArkUI 提供了三个组件标识通用属性,各自面向不同场景。理解它们的边界,是写出规范业务代码的前提:
| 属性 | 签名 | 参数类型 | API 版本 | 核心用途 |
|---|---|---|---|---|
.id() |
id(value: string): T |
string | API 10+ | 业务代码唯一标识,供 getFrameNodeById 查询布局 |
.key() |
key(value: string): T |
string | API 10+ | ForEach diff key / 测试标识(getInspectorByKey 仅限测试文件) |
.restoreId() |
restoreId(value: number): T |
number | API 8+ | 应用被系统回收后重建时,自动恢复可滚动组件的滚动位置 |
index.ets 顶部注释对此做了完整说明:
typescript
// ① .id(string) ------ 唯一字符串 ID,供 FrameNode / componentUtils 查询布局
// ② .key(string) ------ ForEach diff key,帮助框架复用组件节点
// ③ .restoreId(number) ------ 整数恢复标识,应用被系统回收后重建时自动恢复
// 可滚动组件(List / Scroll / Grid / WaterFlow)的滚动位置
重点讲解 .restoreId(),上图中的第三个属性。
二、属性签名与参数说明
typescript
restoreId(value: number): T
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
value |
number | 是 | 非负整数,同一页面内必须唯一;框架以此整数关联快照数据与组件实例 |
适用组件范围(仅以下可滚动组件支持状态恢复):
| 组件 | 恢复的状态内容 |
|---|---|
List |
滚动偏移量(上次停留的位置) |
Scroll |
滚动偏移量 |
Grid |
滚动偏移量 |
WaterFlow |
滚动偏移量 |
对
Column、Row、Text、Button等非滚动组件设置.restoreId()不会报错,但也不会有任何效果。
三、为什么需要 restoreId?
HarmonyOS 会在低内存时主动回收后台应用进程。进程销毁后,用户再次打开应用时,Ability 从零重建,页面恢复到初始状态,用户上次的滚动位置随之丢失。
.restoreId() 通过框架内置的快照机制解决这个问题,整个流程如下:
用户正常使用
→ 将列表滚动到第 80 条
→ 接到来电 / 切到其他应用 → 系统内存不足 → 进程被回收
→ 框架在回收前已将 restoreId=1 对应的滚动位置写入快照
用户重新打开应用
→ Ability 重建,页面重建
→ 框架检测到 restoreId=1 有快照数据
→ List 滚动自动恢复到第 80 条位置
→ 用户无感知,体验连续
与手动保存方案对比:
| 方案 | 监听滚动事件 | 持久化存储 | 重建时读取恢复 | 代码量 |
|---|---|---|---|---|
| 手动(AppStorage / Preferences) | ✅ 需要 | ✅ 需要 | ✅ 需要 | 较多 |
.restoreId() |
❌ 不需要 | ❌ 不需要 | ❌ 不需要 | 一行 |
四、前置配置:module.json5 开启状态恢复
.restoreId() 依赖 Ability 级别的状态恢复开关。未开启时,框架不会记录快照,属性声明了也不会生效:
json5
// entry/src/main/module.json5
{
"module": {
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"restoreEnabled": true // ← 必须添加此字段,restoreId 才会生效
}
]
}
}
五、示例代码解析(对应 index.ets RestoreIdExample)
5.1 完整源码
typescript
@Entry
@Component
struct RestoreIdExample {
// 模拟列表数据:30 条
private listData: number[] = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29];
build() {
Column({ space: 0 }) {
Text('restoreId 示例:滚动位置恢复')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.padding({ top: 16, bottom: 8 })
List({ space: 12 }) {
ForEach(
this.listData,
(item: number) => {
ListItem() {
Text(`列表项 ${item}`)
.width('100%')
.height(60)
.fontSize(16)
.textAlign(TextAlign.Center)
.borderRadius(8)
.backgroundColor(Color.Pink)
}
},
// ForEach 第三个参数:keyGenerator
// 为每个 ListItem 提供唯一 key,对应 .key() 概念
(item: number) => item.toString() // ← ②
)
}
.width('100%')
.layoutWeight(1)
.restoreId(1) // ← ① 状态恢复标识,同页面内唯一整数
}
.width('100%')
.height('100%')
.padding({ left: 16, right: 16 })
}
}
运行结果如图:

5.2 标注解析
标注 ①:.restoreId(1)
链式挂载到 List 组件。参数 1 是页面内的唯一整数标识,框架通过这个整数将滚动快照与具体 List 实例绑定。写法与 .width()、.layoutWeight() 完全一致,无需其他代码。
标注 ②:ForEach keyGenerator(第三个参数)
typescript
(item: number) => item.toString()
这是 .key() 属性的 ForEach 等价形式。框架在 diff 时用此返回值判断哪些 ListItem 是新增/删除/移动的,从而精确复用节点,避免整列表重建。
.restoreId() 与 keyGenerator 的协作关系:
.restoreId(1) → 框架记住"这个 List 滚动到了哪里"
keyGenerator → 框架知道"恢复滚动后,每一项对应哪个数据"
两者配合,列表恢复后内容与位置均与退出前一致
六、唯一性规则与常见错误
6.1 同页面内必须唯一
typescript
// ❌ 错误:两个可滚动组件使用相同的 restoreId,框架行为不可预期
List(...).restoreId(1)
Grid(...).restoreId(1) // 重复!
// ✅ 正确:各自分配不同整数
List(...).restoreId(1)
Grid(...).restoreId(2)
6.2 ForEach 动态渲染多个可滚动组件
typescript
// ❌ 错误:动态生成的多个 List 全部用 restoreId(1)
ForEach(tabs, (tab: Tab) => {
List(...).restoreId(1) // 每个 List 的 restoreId 相同,互相覆盖快照
})
// ✅ 正确:用 index 加偏移量确保唯一(偏移避免与页面其他组件冲突)
ForEach(tabs, (tab: Tab, index: number) => {
List(...).restoreId(100 + index) // 100、101、102 ...
})
6.3 对非滚动组件设置(无效但不报错)
typescript
// ⚠ 不生效:Text、Button 等非滚动组件设置 restoreId 没有任何效果
Text('标题').restoreId(5) // 框架忽略,不报错,也不恢复任何状态
七、与 .id() 联合使用
.restoreId() 与 .id() 面向不同场景,可同时设置,互不干扰:
typescript
List({ space: 12 }) {
// ...
}
.id('mainList') // ← 供业务代码通过 getFrameNodeById 查询布局位置
.restoreId(1) // ← 供框架在 Ability 重建时恢复滚动位置
两个属性的生命周期完全不同:
| 属性 | 谁来使用 | 使用时机 |
|---|---|---|
.id('mainList') |
开发者主动调用 | 布局完成后,随时通过 getFrameNodeById('mainList') 查询 |
.restoreId(1) |
框架自动处理 | Ability 被回收前(写快照)、重建后(读快照恢复),开发者无需介入 |
八、与 .id() / .key() 的完整三属性对比
| 对比项 | .id(string) |
.key(string) |
.restoreId(number) |
|---|---|---|---|
| 参数类型 | string | string | number |
| API 版本 | API 10+ | API 10+ | API 8+ |
| 适用组件 | 所有组件 | 所有组件 | 仅可滚动组件 |
| 主要用途 | 业务查询布局 | ForEach diff / 测试标识 | 跨进程状态恢复 |
| 生效时机 | 布局完成后,按需查询 | 渲染 diff 时 / 测试触发时 | Ability 被回收并重建时 |
| ArkTSCheck | ✅ 无警告 | ⚠ 配合测试 API 在业务代码中报警告 | ✅ 无警告 |
| 需要配置文件 | 否 | 否 | ✅ module.json5 restoreEnabled: true |
十、总结
.restoreId() 是 ArkUI 三个标识属性中使用成本最低 的一个:只需一行链式调用,配合 module.json5 中一个配置字段,就能让可滚动组件在应用进程被系统回收后无缝恢复滚动位置,全程不需要监听、存储、读取。
结合 index.ets 中 RestoreIdExample 的示例,三个标识属性的协作关系可以总结为下面这段代码:
typescript
List({ space: 12 }) {
ForEach(
this.listData,
(item: number) => { ListItem() { ... } },
(item: number) => item.toString() // ← .key() 等价:ForEach diff key
)
}
.id('mainList') // ← .id():供 getFrameNodeById 查询布局
.restoreId(1) // ← .restoreId():供框架恢复滚动位置
三行代码,三个维度,各司其职:
.id()→ 开发者用,随时查位置- ForEach keyGenerator(
.key()等价)→ 框架用,精准 diff 节点 .restoreId()→ 框架用,跨进程恢复滚动
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是持续创作的动力!