LazyForEach与ForEach作用相同,都是循环语句,不同的是LazyForEach提供数据懒加载,也就是当LazyForEach用于滚动容器时,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会销毁并回收组件以降低内存占用。语法上与ForEach写法相同,不同的是LazyForEach需要实现IDataSource接口,并且需要注意的是,在使用LazyForEach进行组件复用时,键值生成函数keyGenerator中不推荐使用stringify。在复杂的业务场景中,使用stringify会对item对象进行序列化,最终把item转换成字符串,这过程需要消耗大量的时间和计算资源,从而导致页面性能降低。
基本代码展示:
IDataSource

export class IndexDataSource implements IDataSource{
  private listeners: DataChangeListener[] = [];
  private dataArray: string[] = [];

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

  getData(index: number): string {
    return this.dataArray[index];
  }

  pushData(data: string): void {
    this.dataArray.push(data);
    this.notifyDataAdd(this.dataArray.length - 1);
  }

  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);
    }
  }

  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded();
    });
  }

  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index);
    });
  }

}

LazyForEach

import { IndexDataSource } from '../data/IndexDataSource'



struct Index {
  private data:IndexDataSource = new IndexDataSource()

  aboutToAppear(): void {
    for (let i = 0; i < 20; i++) {
      this.data.pushData(`这是第${i}个`)

    }
  }

  build() {
    Column() {
      List({ space: 10 }) {
        LazyForEach(this.data, (item: string) => {
          ListItem() {
            Text(item)
              .width("100%")
              .height(50)
              .fontSize(12)
              .fontColor(Color.Black)
              .textAlign(TextAlign.Center)
              .backgroundColor(Color.Green)
          }
        },(item: string)=> item)
      }
      .padding({ left: 10, right: 10 })
    }
  }
}

运行效果图:
在这里插入图片描述
LazyForEach的使用限制:
1、LazyForEach必须在容器组件内使用,仅有List、ListItemGroup、Grid、Swiper以及WaterFlow组件支持数据懒加载(可配置cachedCount属性,即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。支持数据懒加载的父组件根据自身及子组件的高度或宽度计算可视区域内需布局的子节点数量,高度或宽度的缺失会导致部分场景懒加载失效。

2、LazyForEach依赖生成的键值判断是否刷新子组件,键值不变则不触发刷新。

3、容器组件内只能包含一个LazyForEach。以List为例,不建议同时包含ListItem、ForEach、LazyForEach,不建议同时包含多个LazyForEach。

4、LazyForEach在每次迭代中,必须创建且只允许创建一个子组件;即LazyForEach的子组件生成函数有且只有一个根组件。

5、生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。

6、允许LazyForEach包含在if/else条件渲染语句中,也允许LazyForEach中出现if/else条件渲染语句。

7、键值生成器必须针对每个数据生成唯一的值,如果键值相同,将导致键值相同的UI组件渲染出现问题。

8、LazyForEach必须使用一个数据变化监听器DataChangeListener对象进行更新(具体参数使用参考LazyForEach),重新赋值第一个参数dataSource会导致异常;dataSource使用状态变量时,状态变量改变不会触发LazyForEach的UI刷新。

9、为了高性能渲染,使用DataChangeListener对象的onDataChange方法更新UI时,需要生成不同于原来的键值来触发组件刷新。

10、LazyForEach和@Reusable装饰器一起使用能触发节点复用。使用方法:将@Reusable装饰在LazyForEach列表的组件上,见列表滚动配合LazyForEach使用。

11、LazyForEach和@ReusableV2装饰器一起使用能触发节点复用。详见@ReusableV2装饰器指南文档中的在LazyForEach组件中使用。

12、LazyForEach的子节点在离开可视区域和预加载区域时,不会立即被析构或回收,LazyForEach会在空闲时析构或回收这些节点。

懒加载原理
1、LazyForEach会根据屏幕可视区能够容纳显示的组件数量,按需加载数据。
2、根据加载的数据量创建组件,挂载在组件树上,构建出一棵短小的组件树。即,屏幕可以展示多少列表项组件,就按需创建多少个ListItem组件节点挂载在List组件树根节点上。
3、屏幕可视区只展示部分组件。当可视区外的组件需要在屏幕内显示时,需要从头完成数据加载、组件创建、挂载组件树这一过程,直至渲染到屏幕上。

LazyForEach渲染过程:
在这里插入图片描述
虽然在长列表开发中使用LazyForEach可以提升性能表现,但是在快速滑动长列表的过程中,如来不及加载需要显示的列表项,会导致出现白块的现象,这个时候可以使用List容器的cachedCount属性,cachedCount可是设置列表中ListItem/ListItemGroup的预加载数量,但是需要注意的是,cachedCount只能与与LazyForEach一起使用,并且只在LazyForEach中生效。Grid、Swiper以及WaterFlow也都包含cachedCount属性。
使用方法如下:

 List({ space: 10 }) {
        LazyForEach(this.data, (item: string) => {
          ListItem() {
            Text(item)
              .width("100%")
              .height(50)
              .fontSize(12)
              .fontColor(Color.Black)
              .textAlign(TextAlign.Center)
              .backgroundColor(Color.Green)
          }
        },(item: string)=> item)
      }
      .cachedCount(5)
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐