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

📅 2026/7/12 1:25:13 👁️ 阅读次数 📝 编程学习
HarmonyOS 小游戏《对战五子棋》开发第35篇 - 列表渲染性能优化

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

ForEach基本用法

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

(item:LevelConfig)=>item.level.toString()

使用关卡编号的字符串作为key——每个关卡的level是唯一的(1/2/3),保证稳定。

ForEach在关卡列表中的应用

@BuilderlevelSelectView(){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

// 唯一且稳定的ID(item:User)=>item.id.toString()(item:LevelConfig)=>item.level.toString()(item:Product)=>product.sku

差的key

// 数组索引(默认)— 在增删时不稳定(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属性:

.enabled(item.level<=this.unlockedLevel).opacity(item.level<=this.unlockedLevel?1.0:0.5)

因为key(level.toString())不变,ArkUI只更新属性,不重建组件。

ForEach与其他循环的对比

ArkUI中没有v-for或map的等价物——ForEach是唯一的列表渲染方式:

// ✅ 正确:使用ForEachForEach(this.levels,(item:LevelConfig)=>{this.levelCard(item)},(item:LevelConfig)=>item.level.toString())// ❌ 错误:不能用mapthis.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闪烁和状态错乱。