DEV Community

Cover image for next LazyForEach Data Lazy Loading Introduction
liu yang
liu yang

Posted on

next LazyForEach Data Lazy Loading Introduction

LazyForEach Data Lazy Loading Introduction

Overview

LazyForEach is a powerful component for iterating over data and creating components on - demand. When used within a scrollable container, it efficiently manages component creation and destruction based on the visible area, optimizing memory usage.

Usage Restrictions

  • Container Requirements: LazyForEach must be used within a container component. Only List, Grid, Swiper, and WaterFlow components support data lazy loading. These components allow configuration of the cachedCount property, which loads a small buffer of data beyond the visible area. Other components load all data at once.
  • Single Child Component: In each iteration of LazyForEach, exactly one child component must be created.
  • Allowed Child Components: The child components generated must be valid for the parent container.
  • Conditional Rendering: LazyForEach can be used within if/else conditional rendering statements, and conditional statements can also be used within LazyForEach.
  • Unique Keys: The keyGenerator must produce a unique value for each data item. Duplicate keys can cause rendering issues.
  • Data Update Mechanism: LazyForEach requires a DataChangeListener object for updates. When using a state variable as the first parameter dataSource, changes to the state variable won't trigger UI refresh of LazyForEach. To refresh the UI efficiently, use the onDataChange method of the DataChangeListener object with new keys.
  • Reusable Decorator: LazyForEach must be used with the @Reusable decorator to enable node reuse. Apply the @Reusable decorator to the components within the LazyForEach list.

Key Generation Rules

During the iteration process, the system generates a unique and persistent key for each item to identify the component. Changes to this key signal the ArkUI framework to replace or modify the element, creating a new component based on the updated key.

LazyForEach provides a keyGenerator parameter, a function allowing developers to customize key generation rules. If not defined, the ArkUI framework uses a default function: (item: Object, index: number) => { return viewId + '-' + index.toString(); }, where viewId is generated during compilation and remains consistent within the same LazyForEach component.

Component Creation Rules

Once key generation rules are established, the itemGenerator function of LazyForEach creates components for each array item based on these rules. Component creation occurs in two scenarios: initial rendering and subsequent renders.

Implementation Examples

Local Data Simulation

class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = [];
  private originDataArray: string[] = [];

  public totalCount(): number {
    return 0;
  }

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

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

class MyDataSource extends BasicDataSource {
  private dataArray: string[] = [];

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

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

  public addData(index: number, data: string): void {
    this.dataArray.splice(index, 0, data);
    this.notifyDataAdd(index);
  }

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

@Entry
@Component
struct Index {
  private data: MyDataSource = new MyDataSource();

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

  build() {
    List({ space: 3 }) {
      LazyForEach(this.data, (item: string) => {
        ListItem() {
          ChildComponent({ getdata: item });
        }
      }, (item: string) => item);
    }.cachedCount(5);
  }
}

@Reusable
@Component
struct ChildComponent {
  getdata!: string;

  build() {
    Row() {
      Text(this.getdata).fontSize(50).onAppear(() => {
        console.info("appear:" + this.getdata);
      });
    }.margin({ left: 10, right: 10 });
  }
}
Enter fullscreen mode Exit fullscreen mode

Network Request Data Lazy Loading

import Logger from '../utils/Logger';
import { httpRequestGet } from '../utils/OKhttpUtil';
import CommonConstant, * as commonConst from '../common/CommonConstants';
import { Positiondata, PositionModel } from '../bean/PositionModel';

class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = [];
  private originDataArray: Array<Positiondata> = [];

  public totalCount(): number {
    return 0;
  }

  public getData(index: number): Positiondata {
    return this.originDataArray[index];
  }

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

class MyDataSource extends BasicDataSource {
  private dataArray: Array<Positiondata> = [];

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

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

  public addData(index: number, data: Positiondata): void {
    this.dataArray.splice(index, 0, data);
    this.notifyDataAdd(index);
  }

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

@Entry
@Component
struct Index {
  private JokeListdata: MyDataSource = new MyDataSource();

  @State TAG: string = 'Companylist  --- > ';
  @State positiondata: Positiondata = new Positiondata();
  @State JokeList: Array<Positiondata> = [];

  async aboutToAppear() {
    Logger.error(this.TAG + ' aboutToAppear  --- > ');
    let networkurl = CommonConstant.DISH;
    await httpRequestGet(networkurl).then((data) => {
      console.log(this.TAG + "data --- > " + data);
      Logger.error(this.TAG + "Login request callback result ---> " + data.toString());
      let positionmodel: PositionModel = JSON.parse(data.toString());
      this.JokeList = positionmodel.data;
      for (let i = 0; i < this.JokeList.length; i++) {
        this.JokeListdata.pushData(this.JokeList[i]);
      }
    });
  }

  build() {
    List({ space: 3 }) {
      LazyForEach(this.JokeListdata, (item: Positiondata, index: number) => {
        ListItem() {
          ChildComponent({ getdata: item });
        }
      }, (item: string) => item);
    }.cachedCount(5);
  }
}

@Reusable
@Component
struct ChildComponent {
  getdata!: Positiondata;

  build() {
    Row() {
      Column() {
        Row() {
          Text(this.getdata?.name).fontSize(14).fontColor($r('app.color.gray')).margin({ left: 20 });
          Text(this.getdata?.salary).fontSize(20).fontColor($r('app.color.freshRed'))
            .margin({ left: 140 }).align(Alignment.BottomStart);
        }.justifyContent(FlexAlign.Center).width(commonConst.GOODS_LIST_WIDTH);

        Text(this.getdata?.cname)
          .fontSize(25)
          .margin({ left: 10 });
        Divider().width('90%').backgroundColor(Color.Black);
        Text(this.getdata?.username)
          .fontColor($r('app.color.greentext'))
          .fontSize(12)
          .margin({ left: 10, top: 10 });
      }
      .width(commonConst.LAYOUT_WIDTH_OR_HEIGHT)
      .height(commonConst.LAYOUT_WIDTH_OR_HEIGHT)
      .justifyContent(FlexAlign.Start);
    }
    .justifyContent(FlexAlign.Center)
    .height(commonConst.GOODS_LIST_HEIGHT)
    .width(commonConst.LAYOUT_WIDTH_OR_HEIGHT);
  }
}
Enter fullscreen mode Exit fullscreen mode

Data Model

export class PositionModel {
  msg: string = "";
  data: Array<Positiondata> = [];
  code: number = 0;
}

export class Positiondata {
  id: string = "";
  name: string = "";
  cname: string = "";
  size: string = "";
  salary: string = "";
  username: string = "";
  title: string = "";
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)