正确的key让列表渲染更快更稳定

ForEach基本用法
typescript
ForEach(this.levels, (item: LevelConfig) => {
this.levelCard(item)
}, (item: LevelConfig) => item.level.toString())
三个参数:
- 数据源 :
this.levels - itemGenerator :
(item) => { 渲染UI } - keyGenerator :
(item) => item.level.toString()--- 返回唯一key
keyGenerator的作用
ArkUI的ForEach通过key判断哪些列表项需要更新:
数据变化前:[{level:1}, {level:2}, {level:3}]
key列表: ["1", "2", "3"]
删除第2项后:[{level:1}, {level:3}]
key列表: ["1", "3"]
ArkUI对比key:
"1" → 保留(不重绘)
"2" → 不存在 → 销毁
"3" → 保留(不重绘)
结果:只销毁了第2项,第1和第3项保持不变
没有keyGenerator的情况
默认使用数组索引作为key:
数据变化前:[{level:1}, {level:2}, {level:3}]
key列表: ["0", "1", "2"]
删除第2项后:[{level:1}, {level:3}]
key列表: ["0", "1"]
ArkUI对比:
"0" → 保留(但内容从level:1变成level:1)→ 不变
"1" → 保留(但内容从level:2变成level:3)→ 重绘!
"2" → 不存在 → 销毁
结果:第2项被重绘(浪费),且可能闪烁
本项目的keyGenerator
typescript
(item: LevelConfig) => item.level.toString()
使用关卡编号的字符串作为key------每个关卡的level是唯一的(1/2/3),保证稳定。
ForEach在关卡列表中的应用
typescript
@Builder
levelSelectView() {
Column({ space: 16 }) {
Text('选择关卡')
.fontSize(18)
.fontColor('#666666')
.margin({ top: 20, bottom: 8 })
ForEach(this.levels, (item: LevelConfig) => {
this.levelCard(item)
}, (item: LevelConfig) => item.level.toString())
Text(`已解锁 ${this.unlockedLevel}/3 关`)
.fontSize(13)
.fontColor('#999999')
.margin({ top: 8 })
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
}
渲染流程:
- ForEach遍历
this.levels数组 - 对每个item调用keyGenerator生成key
- 对每个item调用itemGenerator渲染
levelCard - 当levels变化时,通过key差异决定更新策略
keyGenerator的最佳实践
好的key
typescript
// 唯一且稳定的ID
(item: User) => item.id.toString()
(item: LevelConfig) => item.level.toString()
(item: Product) => product.sku
差的key
typescript
// 数组索引(默认)--- 在增删时不稳定
(item: any, index: number) => index.toString()
// 可能重复的值
(item: User) => item.name // 可能重名
// 会变化的值
(item: Product) => item.price.toString() // 价格变了key就变了
ForEach的更新机制
状态变化触发ForEach重新评估
↓
生成新key列表
↓
与旧key列表对比
├─ 旧key不在新列表 → 销毁该项组件
├─ 新key不在旧列表 → 创建新组件
└─ key相同 → 保留组件,更新@Prop
本项目的列表变化场景
关卡列表this.levels是静态的------不会增删。但unlockedLevel变化时,ForEach不会重新渲染整个列表,只会更新卡片的enabled和opacity属性:
typescript
.enabled(item.level <= this.unlockedLevel)
.opacity(item.level <= this.unlockedLevel ? 1.0 : 0.5)
因为key(level.toString())不变,ArkUI只更新属性,不重建组件。
ForEach与其他循环的对比
ArkUI中没有v-for或map的等价物------ForEach是唯一的列表渲染方式:
typescript
// ✅ 正确:使用ForEach
ForEach(this.levels, (item: LevelConfig) => {
this.levelCard(item)
}, (item: LevelConfig) => item.level.toString())
// ❌ 错误:不能用map
this.levels.map((item: LevelConfig) => {
this.levelCard(item) // build()中不能使用map
})
总结
ForEach是ArkUI列表渲染的核心:
- 三个参数:数据源、itemGenerator、keyGenerator
- key的重要性:稳定唯一的key避免不必要的重绘
- 差异更新:只更新变化的项,提升性能
- 推荐key:业务唯一ID > 数组索引
- 唯一方式:ForEach是ArkUI中唯一的列表渲染方法
正确的keyGenerator不仅能提升性能,还能避免列表更新时的UI闪烁和状态错乱。