为什么要使用懒加载-LazyForEach
我们在使用ForEach进行循环遍历的时候,框架会加载所有的数据源,这样就会导致如果遍历内容太多,占据大量的内存占用,进而导致性能降低。
LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。 当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。
使用过程
官网方式
1 实现提供的一个 IDataSource 的接口
ArkTS类BasicDataSource实现了数据源功能,主要用于配合LazyForEach组件使用,支持数据监听和通知机制:
存储监听器和数据:通过listeners数组存储数据变化监听器,originDataArray存储原始数据。
获取数据总数:totalCount()方法固定返回0,表示不支持动态总数展示。
获取指定索引数据:getData(index)方法根据索引返回数据项。
监听器管理:提供registerDataChangeListener和unregisterDataChangeListener方法来添加或移除监听器。
数据变化通知:通过notifyDataReload、notifyDataAdd、notifyDataChange、notifyDataDelete和notifyDataMove方法通知监听器进行数据重载、添加、修改、删除及移动操作。
/**
* BasicDataSource类是一个数据源的实现,用于配合LazyForEach组件使用。
* 它提供了数据监听器的注册与注销功能,并能通知LazyForEach组件数据变化,
* 以便组件能够相应地更新自身。
*/
export class BasicDataSource implements IDataSource {
// 存储数据变化监听器的数组
private listeners: DataChangeListener[] = [];
// 存储原始数据的数组
private originDataArray: string[] = [];
/**
* 返回数据总数。
* 目前总是返回0,表示该数据源不支持动态数据总数的展示。
* @returns number 总数据数,目前固定为0。
*/
public totalCount(): number {
return 0;
}
/**
* 根据索引获取数据。
* @param index 数据在数组中的索引。
* @returns string 索引对应的数据项。
*/
public getData(index: number): string {
return this.originDataArray[index];
}
/**
* 注册数据变化监听器。
* 框架侧调用此方法向数据源添加监听器,以监听数据变化。
* @param listener DataChangeListener类型的监听器。
*/
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
console.info('add listener');
this.listeners.push(listener);
}
}
/**
* 注销数据变化监听器。
* 框架侧调用此方法从数据源处去除监听器。
* @param listener DataChangeListener类型的监听器。
*/
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
console.info('remove listener');
this.listeners.splice(pos, 1);
}
}
/**
* 通知所有监听器数据需要重新加载。
* 此方法告知所有注册的监听器,数据源中的所有数据已变更,需要重新加载所有子组件。
*/
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded();
})
}
/**
* 通知监听器在特定索引位置添加数据。
* @param index 添加数据的索引位置。
*/
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index);
})
}
/**
* 通知监听器数据在特定索引位置已变更。
* @param index 数据变更的索引位置。
*/
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index);
})
}
/**
* 通知监听器在特定索引位置删除数据。
* @param index 删除数据的索引位置。
*/
notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index);
})
}
/**
* 通知监听器在特定索引位置移动数据。
* @param from 移动数据的起始索引位置。
* @param to 移动数据的目标索引位置。
*/
notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMove(from, to);
})
}
}
2 将数据包装到对象中,实现一系列增删改查的方法
该类 MyDataSource 继承自 BasicDataSource,用于管理字符串数据数组,实现以下功能:
统计数组长度:totalCount() 方法返回数组中元素的数量。
按索引获取数据:getData(index) 通过索引值获取对应位置的数据。
插入数据并通知:addData(index, data) 在指定索引位置插入数据,并通知数据变化。
追加数据并通知:pushData(data) 在数组末尾添加数据,并通知数据变化。
刷新数据源:reloadData(data) 更新内部数据数组,并通知数据重新加载,常用于界面重新渲染。
import { BasicDataSource } from './BasicDataSource';
/**
* MyDataSource 类继承自 BasicDataSource,用于管理字符串数据数组
* 它实现了数据的添加、推送以及通过索引获取数据的功能
*/
export class MyDataSource extends BasicDataSource {
// 数据数组,用于存储字符串数据
private dataArray: string[] = [];
/**
* 获取数据总条数
*
* @returns 数据数组中的元素数量
*/
public totalCount(): number {
return this.dataArray.length;
}
/**
* 根据索引获取数据
*
* @param index 索引值,用于指定要获取的数据位置
* @returns 索引位置的数据
*/
public getData(index: number): string {
return this.dataArray[index];
}
/**
* 在指定索引位置添加数据,并通知数据已添加
*
* @param index 要添加数据的索引位置
* @param data 要添加的数据
*/
public addData(index: number, data: string): void {
this.dataArray.splice(index, 0, data);
this.notifyDataAdd(index);
}
/**
* 在数组末尾添加数据,并通知数据已添加
*
* @param data 要添加的数据
*/
public pushData(data: string): void {
this.dataArray.push(data);
this.notifyDataAdd(this.dataArray.length - 1);
}
}
3 实现效果
import { MyDataSource } from './MyDataSource';
/**
* Index组件的主入口,负责数据源的初始化和组件的构建
*/
@Entry
@Component
struct Index {
// 初始化数据源为MyDataSource实例
data: MyDataSource = new MyDataSource()
/**
* 在组件即将出现时调用,用于加载数据
*/
aboutToAppear(): void {
// 向数据源中添加三条数据
this.data.pushData('app.media.1')
this.data.pushData('app.media.2')
this.data.pushData('app.media.3')
}
/**
* 构建组件的UI
* 使用Column布局,并在其中使用Swiper组件结合LazyForEach实现轮播效果
*/
build() {
Column() {
Swiper() {
// 使用LazyForEach结合数据源实现性能优化的循环渲染
LazyForEach(this.data, (item: string) => {
Image($r(item)) // 对每个数据项创建Image组件
}, (item: string) => item) // 使用item作为key优化渲染性能
}
.width('100%') // 设置Swiper宽度
.autoPlay(true) // 开启自动播放
}
.width('100%') // 设置Column宽度
.height('100%') // 设置Column高度
}
}
不推荐官网方式的原因
- 数据类型写死,无法实现复用
- 步骤过于繁琐,内容太多
推荐方法
我们竟然无法定义为泛型实现复用,但又不想写这么多
我们为什么不定义一个类,实现IDataSource 接口
export class MyDataSource implements IDataSource {
// 数据数组,用于存储字符串数据
dataArray: string[] = [];
// 存储数据变化监听器的数组
listeners: DataChangeListener[] = [];
/**
* 获取数据总条数
*
* @returns 数据数组中的元素数量
*/
public totalCount(): number {
return this.dataArray.length;
}
/**
* 根据索引获取数据
*
* @param index 索引值,用于指定要获取的数据位置
* @returns 索引位置的数据
*/
public getData(index: number): string {
return this.dataArray[index];
}
/**
* 在指定索引位置添加数据,并通知数据已添加
*
* @param index 要添加数据的索引位置
* @param data 要添加的数据
*/
public addData(index: number, data: string): void {
this.dataArray.splice(index, 0, data);
this.notifyDataAdd(index);
}
/**
* 在数组末尾添加数据,并通知数据已添加
*
* @param data 要添加的数据
*/
public pushData(data: string): void {
this.dataArray.push(data);
this.notifyDataAdd(this.dataArray.length - 1);
}
/**
* 注册数据变化监听器。
* 框架侧调用此方法向数据源添加监听器,以监听数据变化。
* @param listener DataChangeListener类型的监听器。
*/
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
console.info('add listener');
this.listeners.push(listener);
}
}
/**
* 注销数据变化监听器。
* 框架侧调用此方法从数据源处去除监听器。
* @param listener DataChangeListener类型的监听器。
*/
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
console.info('remove listener');
this.listeners.splice(pos, 1);
}
}
/**
* 通知所有监听器数据需要重新加载。
* 此方法告知所有注册的监听器,数据源中的所有数据已变更,需要重新加载所有子组件。
*/
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded();
})
}
/**
* 通知监听器在特定索引位置添加数据。
* @param index 添加数据的索引位置。
*/
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index);
})
}
/**
* 通知监听器数据在特定索引位置已变更。
* @param index 数据变更的索引位置。
*/
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index);
})
}
/**
* 通知监听器在特定索引位置删除数据。
* @param index 删除数据的索引位置。
*/
notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index);
})
}
/**
* 通知监听器在特定索引位置移动数据。
* @param from 移动数据的起始索引位置。
* @param to 移动数据的目标索引位置。
*/
notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMove(from, to);
})
}
}
实现封装
封装数据源 实现 IDataSource接口
export class MyDataSource<T> implements IDataSource {
// 数据数组,用于存储字符串数据
dataArray: T[] = [];
// 存储数据变化监听器的数组
listeners: DataChangeListener[] = [];
public totalCount(): number {
return this.dataArray.length;
}
public getData(index: number): T {
return this.dataArray[index];
}
public addData(index: number, data: T): void {
this.dataArray.splice(index, 0, data);
this.notifyDataAdd(index);
}
public pushData(data: T): void {
this.dataArray.push(data);
this.notifyDataAdd(this.dataArray.length - 1);
}
// 注册数据变化
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
console.info('add listener');
this.listeners.push(listener);
}
}
// 注销数据变化
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
console.info('remove listener');
this.listeners.splice(pos, 1);
}
}
// 通知所有监听器数据需要重新加载
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded();
})
}
// 通知监听器在特定索引位置添加数据
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index);
})
}
// 通知监听器数据在特定索引位置已变更
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index);
})
}
// 通知监听器在特定索引位置删除数据
notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index);
})
}
// 通知监听器在特定索引位置移动数据
notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMove(from, to);
})
}
}
实现
import { MyDataSource } from './MyDataSource';
/**
* Index组件的主入口,负责数据源的初始化和组件的构建
*/
@Entry
@Component
struct Index {
// 初始化数据源为MyDataSource实例
data: MyDataSource<string> = new MyDataSource()
/**
* 在组件即将出现时调用,用于加载数据
*/
aboutToAppear(): void {
// 向数据源中添加三条数据
this.data.pushData('app.media.1')
this.data.pushData('app.media.2')
this.data.pushData('app.media.3')
}
/**
* 构建组件的UI
* 使用Column布局,并在其中使用Swiper组件结合LazyForEach实现轮播效果
*/
build() {
Column() {
Swiper() {
// 使用LazyForEach结合数据源实现性能优化的循环渲染
LazyForEach(this.data, (item: string) => {
Image($r(item)) // 对每个数据项创建Image组件
}, (item: string) => item) // 使用item作为key优化渲染性能
}
.width('100%') // 设置Swiper宽度
.autoPlay(true) // 开启自动播放
}
.width('100%') // 设置Column宽度
.height('100%') // 设置Column高度
}
}
总结
LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。