在鸿蒙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
在 List、Grid 或 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必须用在支持懒加载的容器组件内,如List、Grid、Swiper和WaterFlow等。在普通Column或Row中使用将不会生效。 - 避免额外滑动容器嵌套:不要在
List或Grid外层再套一个Scroll组件。这会导致滚动事件冲突,使LazyForEach的懒加载机制失效,造成所有数据一次性加载。 cachedCount设置要合理:cachedCount用于设置可视区域外预加载的组件数量,可提升滑动流畅度。但并非越大越好,过大的缓存值会提前创建更多组件,增加内存和CPU负担,尤其在列表项包含图片时,可能导致卡顿。@Reusable与数据复用:@Reusable装饰器用于组件复用,可进一步提升性能。但使用时需注意,被复用的组件可能会展示旧数据,因此必须在aboutToReuse()生命周期回调中,用新数据正确更新组件的状态,否则会出现数据错乱。itemGenerator的限制:在itemGenerator函数中,每次迭代只能且必须生成一个子组件。同时,函数体内不允许再嵌套使用ForEach或LazyForEach。
简单来说,用好 LazyForEach 的关键在于两点:一是实现一个支持精准通知的 IDataSource,二是在使用时务必提供稳定的 keyGenerator。