鸿蒙ArkTS下 LazyForEach懒加载
鸿蒙ArkTS下 LazyForEach懒加载

鸿蒙ArkTS下 LazyForEach懒加载

在鸿蒙ArkTS中,LazyForEach 是实现长列表高性能渲染的关键。它通过按需加载节点复用,能显著提升大数据量列表的滑动流畅度并降低内存占用

它的核心思想是:当列表滚动时,只创建和渲染用户可视区域内的组件,一旦滑出视野,组件就会被销毁并回收内存

🧬 第一步:实现数据源(IDataSource)

这是使用 LazyForEach 的前提。你需要创建一个类,实现 IDataSource 接口,这个接口充当了数据和UI之间的桥梁。

typescript

// 1. 定义数据类型
class MyItemData {
  id: string
  name: string
  constructor(id: string, name: string) {
    this.id = id
    this.name = name
  }
}

// 2. 实现 IDataSource 接口
class MyDataSource implements IDataSource {
  private dataArray: MyItemData[] = []
  private listeners: DataChangeListener[] = []

  constructor(data: MyItemData[]) {
    this.dataArray = data
  }

  // 必须实现:获取数据总数
  totalCount(): number {
    return this.dataArray.length
  }

  // 必须实现:根据索引获取数据
  getData(index: number): MyItemData {
    return this.dataArray[index]
  }

  // 必须实现:注册数据变化监听器
  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      this.listeners.push(listener)
    }
  }

  // 必须实现:取消注册数据变化监听器
  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener)
    if (pos >= 0) {
      this.listeners.splice(pos, 1)
    }
  }

  // --- 以下为自定义的数据操作方法,用于更新数据和通知UI ---

  // 添加数据(示例)
  addData(item: MyItemData): void {
    this.dataArray.push(item)
    // 通知监听器数据已重新加载(简单场景)
    // 更优的做法是使用 onDataAdd(index, count) 进行精准通知[reference:4]
    this.notifyDataReload()
  }

  // 通知所有监听器数据已刷新
  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded()
    })
  }
}

🧩 第二步:在组件中使用 LazyForEach

在 ListGrid 或 Swiper 等容器组件内,使用 LazyForEach 来渲染数据

typescript

@Entry
@Component
struct MyListPage {
// 初始化数据源
private dataSource: MyDataSource = new MyDataSource([
new MyItemData('1', 'Item 1'),
new MyItemData('2', 'Item 2'),
// ... 更多数据
])

build() {
List() {
LazyForEach(
// 参数1: 数据源
this.dataSource,
// 参数2: 根据数据项生成UI组件
(item: MyItemData, index: number) => {
ListItem() {
Text(item.name)
.fontSize(16)
.width('100%')
.height(50)
}
},
// 参数3: (强烈推荐) 键值生成器,用于唯一标识每个列表项[reference:6],如果省略,默认使用index作为键
(item: MyItemData, index: number) => {
return item.id // 使用数据中唯一且稳定的字段作为key,同时使用index作为补充,避免删除元素的情况下,复用index导致出现数据显示异常的问题
}
)
}
.width('100%')
.height('100%')
}
}

⚠️ 注意事项与避坑指南

  • 务必实现 keyGenerator:它是性能优化的关键。一个稳定且唯一的键值(如数据的 id)能帮助框架精准识别数据变化,避免不必要的组件重建。若不提供,在数据增删或排序时,可能导致所有列表项重建,严重影响性能
  • 精准通知,避免全量刷新:当数据发生变化(增、删、改)时,应调用 DataChangeListener 中对应的精准方法(如 onDataAdd()onDataDelete()onDataChange()),而不是简单粗暴地调用 onDataReloaded()。全量刷新会失去懒加载的性能优势。
  • 使用场景限制LazyForEach 必须用在支持懒加载的容器组件内,如 ListGridSwiper 和 WaterFlow 等。在普通 Column 或 Row 中使用将不会生效。
  • 避免额外滑动容器嵌套:不要在 List 或 Grid 外层再套一个 Scroll 组件。这会导致滚动事件冲突,使 LazyForEach 的懒加载机制失效,造成所有数据一次性加载。
  • cachedCount 设置要合理cachedCount 用于设置可视区域外预加载的组件数量,可提升滑动流畅度。但并非越大越好,过大的缓存值会提前创建更多组件,增加内存和CPU负担,尤其在列表项包含图片时,可能导致卡顿。
  • @Reusable 与数据复用@Reusable 装饰器用于组件复用,可进一步提升性能。但使用时需注意,被复用的组件可能会展示旧数据,因此必须在 aboutToReuse() 生命周期回调中,用新数据正确更新组件的状态,否则会出现数据错乱。
  • itemGenerator 的限制:在 itemGenerator 函数中,每次迭代只能且必须生成一个子组件。同时,函数体内不允许再嵌套使用 ForEach 或 LazyForEach

简单来说,用好 LazyForEach 的关键在于两点:一是实现一个支持精准通知的 IDataSource,二是在使用时务必提供稳定的 keyGenerator

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注