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闪烁和状态错乱。

相关推荐
FF2501_940228583 小时前
HarmonyOS应用开发实战:猫猫大作战-在排行榜和历史记录查询中,排序是必不可少的操作【apple_product_name】
华为·harmonyos·鸿蒙
程序员黑豆8 小时前
鸿蒙应用开发:@Provider 与 @Consumer 跨组件双向同步详解
前端·harmonyos
程序员黑豆9 小时前
鸿蒙应用开发之跨组件传参:@Provide 与 @Consume 跨层级数据同步详解
前端·harmonyos
懿路向前9 小时前
【HarmonyOS学习笔记】2026-07-30 | 小艺开放平台智能体接入实战
笔记·学习·harmonyos
大锅盖19 小时前
HarmonyOS ArkTS 的新手练手样例:从 Text 和 Button 开始,做一个会变化的计数页面
华为·harmonyos
袁震10 小时前
小图传输,大图呈现——用 HarmonyOS 7 端侧 AI 实现 4 倍图像超分重建
人工智能·华为·harmonyos
结网的兔子11 小时前
【前端开发】Web端迁移至 uni-app 及鸿蒙扩展方案对比
前端·uni-app·harmonyos
落叶飘飘s12 小时前
餐饮服务与软件创新的融合:解析海底捞 APP 的 Flutter 鸿蒙开发之路
flutter·华为·harmonyos
qizayaoshuap13 小时前
# HarmonyOS ArkTS 滑动删除列表实战(三):交互式列表增删操作
华为·harmonyos
GitCode官方14 小时前
openPangu-2.0-Pro 模型及技术报告正式开源上线 AtomGit AI
人工智能·华为·开源·harmonyos·atomgit