HarmonyOS 小游戏《对战五子棋》开发第35篇 - 列表渲染性能优化

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

ForEach基本用法

typescript 复制代码
ForEach(this.levels, (item: LevelConfig) => {
  this.levelCard(item)
}, (item: LevelConfig) => item.level.toString())

三个参数:

  1. 数据源this.levels
  2. itemGenerator(item) => { 渲染UI }
  3. 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)
}

渲染流程

  1. ForEach遍历this.levels数组
  2. 对每个item调用keyGenerator生成key
  3. 对每个item调用itemGenerator渲染levelCard
  4. 当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不会重新渲染整个列表,只会更新卡片的enabledopacity属性:

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列表渲染的核心:

  1. 三个参数:数据源、itemGenerator、keyGenerator
  2. key的重要性:稳定唯一的key避免不必要的重绘
  3. 差异更新:只更新变化的项,提升性能
  4. 推荐key:业务唯一ID > 数组索引
  5. 唯一方式:ForEach是ArkUI中唯一的列表渲染方法

正确的keyGenerator不仅能提升性能,还能避免列表更新时的UI闪烁和状态错乱。

相关推荐
xianjixiance_2 小时前
鸿蒙实战:SharePage 报告分享——雷达图预览与 pasteboard 剪贴板
华为·harmonyos
全堆鸿蒙12 小时前
27 RdbPredicates 条件查询详解:EqualTo、OrderBy、组合条件
华为·harmonyos·鸿蒙系统
心中有国也有家12 小时前
AtomGit Flutter 鸿蒙客户端: ChangeNotifier 模式
学习·flutter·华为·harmonyos
国服第二切图仔13 小时前
HarmonyOS APP《画伴梦工厂》开发第59篇-碰一碰精准分享——跨设备素材插入
华为·harmonyos
hqzing13 小时前
在鸿蒙 PC 上使用 Claude Code(最新的 Bun 版本)
华为·harmonyos
xd18557855514 小时前
鸿蒙AI菜谱生成器:基于ArkTS原生开发与鸿蒙PC适配实战
人工智能·华为·harmonyos·鸿蒙
GitCode官方15 小时前
开源鸿蒙跨平台直播|QRN 跨端鸿蒙一体化实战
人工智能·华为·开源·harmonyos·atomgit
ZZZMMM.zip15 小时前
演示架构师-PPT大纲生成的HarmonyOS开发实践
人工智能·华为·powerpoint·harmonyos·鸿蒙·鸿蒙系统
国服第二切图仔16 小时前
HarmonyOS APP《画伴梦工厂》开发第60篇-分布式软总线2.0——多设备协同新范式
分布式·wpf·harmonyos