[鸿蒙][性能优化]ForEach与LazyForEach有什么区别?

以下是 ForEach 的语法及其与 LazyForEach 的区别。

ForEach 的语法

ForEach 的基本使用方式如下:

TS 复制代码
typescript
复制代码
ForEach(
  arr: Array<any>,                                   // 数据源数组
  itemGenerator: (item: any, index?: number) => void, // 子组件生成函数
  keyGenerator?: (item: any, index?: number) => string // 键值生成函数(可选)
)

示例代码

以下是 ForEach 的一个完整示例:

TS 复制代码
typescript
复制代码
@Entry
@Component
struct Parent {
  @State simpleList: Array<string> = ['one', 'two', 'three'];

  build() {
    Row() {
      Column() {
        ForEach(this.simpleList, (item: string, index: number) => {
          ChildItem({ item: item })
        }, (item: string, index: number) => item)
      }
      .width('100%')
      .height('100%')
    }
    .height('100%')
    .backgroundColor(0xF1F3F5)
  }
}

@Component
struct ChildItem {
  @Prop item: string;

  build() {
    Text(this.item)
      .fontSize(50)
  }
}

LazyForEach 的语法

LazyForEach 的基本使用方式如下:

TS 复制代码
typescript
复制代码
LazyForEach(
  dataSource: IDataSource,                             // 数据源
  itemGenerator: (item: any, index: number) => void,   // 子组件生成函数
  keyGenerator?: (item: any, index: number) => string // 键值生成函数(可选)
)

示例代码

以下是 LazyForEach 的一个完整示例:

TS 复制代码
typescript
复制代码
@Entry
@Component
struct TestLazyForEachPage {
  @State message: string = '点击全部改变';
  private data: MyDataSource = new MyDataSource();

  aboutToAppear() {
    for (let i = 100; i >= 80; i--) {
      this.data.pushData(`Hello ${i}`);
    }
  }

  build() {
    Column() {
      Text(this.message)
        .width('100%')
        .height(80)
        .fontSize(25)
        .onClick(() => {
          let newData: string[] = [];
          for (let i = 10; i <= 30; i++) {
            newData.push(`Hello ${i}`);
          }
          this.data.freshData(newData);
          Prompt.showToast({ message: '改变了' });
        });

      List({ space: 3 }) {
        LazyForEach(this.data, (item: string, index: number) => {
          ListItem() {
            Row() {
              Text(item).fontSize(50)
                .onAppear(() => {
                  console.info("appear:" + item);
                });
            }.margin({ left: 10, right: 10 });
          }
          .onClick(() => {
            this.data.pushData(`Hello ${this.data.totalCount()}`);
          });
        }, (item: string, index: number) => item + index);
      }.cachedCount(5);
    }
    .width('100%')
    .height('100%');
  }
}

ForEach 与 LazyForEach 的区别

  1. 数据源类型

    • ForEach:直接接受一个数组作为数据源。
    • LazyForEach:接受一个实现了 IDataSource 接口的对象作为数据源。
  2. 渲染策略

    • ForEach:一次性渲染所有数据项,适用于数据量较少的情况。
    • LazyForEach:按需渲染数据项,只渲染可视区域内的数据项,适用于数据量较大的情况,提升性能。
  3. 内存使用

    • ForEach:会一次性加载所有的数据项,内存使用较高。
    • LazyForEach:根据可视区域按需加载数据项,并回收滑出可视区域的数据项,内存使用较低。
  4. 组件复用

    • ForEach:没有内置的组件复用机制。
    • LazyForEach:类似于原生开发中的 cell 复用机制,滑出可视区域的组件会被回收,新出现的组件优先使用复用池中的组件。
  5. 性能优化

    • ForEach:适用于数据量较少且性能要求不高的场景。
    • LazyForEach:适用于数据量较大且性能要求较高的场景,显著提升页面性能和用户体验。
  6. 数据监听

    • ForEach:没有专门的数据监听机制,依赖于数组变化触发的 UI 更新。
    • LazyForEach:需要通过 DataChangeListener 来监听数据变化,手动通知数据变动以刷新 UI。

使用场景

  • ForEach:适用于静态数据或数据量较小的场景,例如简单的列表展示。
  • LazyForEach:适用于动态数据或数据量较大的场景,例如新闻列表、商品列表等需要懒加载的页面。

通过理解上述区别,开发者可以根据具体需求选择合适的渲染方式来优化性能和用户体验。

另外, 关于LazyForEach实现IDataSource 接口

为什么需要实现 IDataSource 接口?

LazyForEach 组件用于处理大数据量时的按需渲染和懒加载,IDataSource 接口提供了一种标准化的方法来管理和访问这些数据。通过实现 IDataSource 接口,可以确保 LazyForEach 组件能够高效地处理数据,并在数据发生变化时正确地刷新 UI。

IDataSource 接口的作用

IDataSource 接口定义了一组方法,用于获取数据、监听数据变化等操作。具体包括:

  1. totalCount() : 返回数据源中的数据总数。
  2. getData(index: number) : 根据索引获取对应的数据项。
  3. registerDataChangeListener(listener: DataChangeListener) : 注册数据变化的监听器。
  4. unregisterDataChangeListener(listener: DataChangeListener) : 注销数据变化的监听器。
  5. notifyDataReload() : 通知组件重新加载所有数据。
  6. notifyDataAdd(index: number) : 通知组件在指定索引处添加数据。
  7. notifyDataChange(index: number) : 通知组件在指定索引处的数据发生变化。
  8. notifyDataDelete(index: number) : 通知组件在指定索引处删除数据。

实现 IDataSource 接口

通过实现 IDataSource 接口,开发者可以自定义数据源的行为,并确保 LazyForEach 能够正确地处理数据加载、数据变化等操作。下面是一个完整的实现示例:

TS 复制代码
typescript
复制代码
interface IDataSource {
  totalCount(): number;
  getData(index: number): any;
  registerDataChangeListener(listener: DataChangeListener): void;
  unregisterDataChangeListener(listener: DataChangeListener): void;
  notifyDataReload(): void;
  notifyDataAdd(index: number): void;
  notifyDataChange(index: number): void;
  notifyDataDelete(index: number): void;
}

interface DataChangeListener {
  onDataReloaded(): void;
  onDataAdd(index: number): void;
  onDataChange(index: number): void;
  onDataDelete(index: number): void;
}

class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = [];
  private data: any[] = [];

  totalCount(): number {
    return this.data.length;
  }

  getData(index: number): any {
    return this.data[index];
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      this.listeners.push(listener);
    }
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const index = this.listeners.indexOf(listener);
    if (index >= 0) {
      this.listeners.splice(index, 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));
  }

  // Methods to manipulate data
  addData(data: any): void {
    this.data.push(data);
    this.notifyDataAdd(this.data.length - 1);
  }

  updateData(index: number, data: any): void {
    this.data[index] = data;
    this.notifyDataChange(index);
  }

  deleteData(index: number): void {
    this.data.splice(index, 1);
    this.notifyDataDelete(index);
  }
}

使用 LazyForEach 与 IDataSource

在使用 LazyForEach 时,需要将数据源传递给 LazyForEach 组件,并通过实现的 IDataSource 接口来管理数据。以下是使用 LazyForEach 的示例:

TS 复制代码
typescript
复制代码
@Entry
@Component
struct TestLazyForEachPage {
  private data: BasicDataSource = new BasicDataSource();

  aboutToAppear() {
    for (let i = 0; i < 20; i++) {
      this.data.addData(`Item ${i}`);
    }
  }

  build() {
    Column() {
      List({ space: 3 }) {
        LazyForEach(this.data, (item: string, index: number) => {
          ListItem() {
            Row() {
              Text(item)
                .fontSize(20)
                .onAppear(() => {
                  console.info(`appear: ${item}`);
                });
            }.margin({ left: 10, right: 10 });
          }
          .onClick(() => {
            this.data.updateData(index, `${item} (clicked)`);
          });
        }, (item: string, index: number) => `${item}_${index}`);
      }.cachedCount(5);
    }
    .width('100%')
    .height('100%');
  }
}

结论

通过实现 IDataSource 接口,可以为 LazyForEach 提供灵活、高效的数据管理方式,确保在处理大数据量时能够按需加载数据,提升性能和用户体验。这也是 LazyForEach 与 ForEach 的主要区别之一:ForEach 适用于小数据量的全量渲染,而 LazyForEach 则专注于大数据量的按需渲染和懒加载。

相关推荐
Android技术栈2 小时前
鸿蒙数据防泄漏(DLP)【Data Loss Prevention Kit开发指导】
程序员·移动开发·数据安全·harmonyos·鸿蒙·openharmony·防泄漏
pixle02 小时前
HarmonyOS Next系列之Echarts图表组件(折线图、柱状图、饼图等)实现(八)
华为·echarts·harmonyos
爱桥代码的程序媛3 小时前
鸿蒙开发管理:【@ohos.account.distributedAccount (分布式帐号管理)】
程序员·移动开发·harmonyos·鸿蒙·鸿蒙系统·openharmony·鸿蒙开发
XuZhenhao060917 小时前
HarmonyOS - 通过.p7b文件获取fingerprint
华为·harmonyos
全栈探索者20 小时前
玩转HarmonyOS NEXT之配置文件篇
harmonyos
不知名靓仔1 天前
鸿蒙应用APP开发实战:详细操作指南
华为·harmonyos
碎像1 天前
我使用HarmonyOs Next开发了b站的首页
华为·harmonyos
Android技术栈2 天前
鸿蒙开发Ability Kit(程序访问控制):【安全控件概述】
程序员·移动开发·harmonyos·鸿蒙·openharmony·访问控制·安全控件
硅纪元2 天前
硅纪元视角 | 国内首款鸿蒙人形机器人“夸父”开启应用新篇章
华为·机器人·harmonyos
李洋-蛟龙腾飞公司2 天前
HarmonyOS Next 原生应用开发-从TS到ArkTS的适配规则(三)
harmonyos