• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# LazyForEach: Lazy Data Loading
2
3
4**LazyForEach** iterates over provided data sources and creates corresponding components during each iteration. When **LazyForEach** is used in a scrolling container, the framework creates components as required within the visible area of the scrolling container. When a component is out of the visible area, the framework destroys and reclaims the component to reduce memory usage.
5
6
7## API Description
8
9
10```ts
11LazyForEach(
12    dataSource: IDataSource,             // Data source to iterate over
13    itemGenerator: (item: any) => void,  // Child component generation function
14    keyGenerator?: (item: any) => string // (Optional). ID generation function
15): void
16interface IDataSource {
17    totalCount(): number;                                             // Get total count of data
18    getData(index: number): any;                                      // Get single data by index
19    registerDataChangeListener(listener: DataChangeListener): void;   // Register listener to listening data changes
20    unregisterDataChangeListener(listener: DataChangeListener): void; // Unregister listener
21}
22interface DataChangeListener {
23    onDataReloaded(): void;                      // Called while data reloaded
24    onDataAdd(index: number): void;            // Called while single data added
25    onDataMove(from: number, to: number): void; // Called while single data moved
26    onDataDelete(index: number): void;          // Called while single data deleted
27    onDataChange(index: number): void;          // Called while single data changed
28}
29```
30
31**Parameters**
32
33
34| Name          | Type                                   | Mandatory  | Description                                    |
35| ------------- | --------------------------------------- | ---- | ---------------------------------------- |
36| dataSource    | IDataSource                             | Yes   | **LazyForEach** data source. You need to implement related APIs.             |
37| itemGenerator | (item: any) =&gt; void   | Yes   | Child component generation function, which generates a child component for each data item in the array.<br>**NOTE**<br>The function body of **itemGenerator** must be included in braces {...}. **itemGenerator** can and must generate only one child component for each iteration. The **if** statement is allowed in **itemGenerator**, but you must ensure that each branch of the **if** statement creates a child component of the same type. **ForEach** and **LazyForEach** statements are not allowed in **itemGenerator**.|
38| keyGenerator  | (item: any) =&gt; string | No   | ID generation function, which generates a unique and fixed ID for each data item in the data source. This ID must remain unchanged for the data item even when the item is relocated in the array. When the item is replaced by a new item, the ID of the new item must be different from that of the replaced item. This ID generation function is optional. However, for performance reasons, it is strongly recommended that the ID generation function be provided, so that the framework can better identify array changes. For example, if no ID generation function is provided, a reverse of an array will result in rebuilding of all nodes in **LazyForEach**.<br>**NOTE**<br>The ID generated for each data item in the data source must be unique.|
39
40
41## Description of IDataSource
42
43
44```ts
45interface IDataSource {
46    totalCount(): number;
47    getData(index: number): any;
48    registerDataChangeListener(listener: DataChangeListener): void;
49    unregisterDataChangeListener(listener: DataChangeListener): void;
50}
51```
52
53| Declaration                                    | Parameter Type              | Description                                   |
54| ---------------------------------------- | ------------------ | ------------------------------------- |
55| totalCount(): number                | -                  | Obtains the total number of data records.                              |
56| getData(index: number): any    | number             | Obtains the data record corresponding to the specified index.<br>**index**: index of the data record to obtain.|
57| registerDataChangeListener(listener:DataChangeListener): void | DataChangeListener | Registers a listener for data changes.<br>**listener**: listener for data changes.     |
58| unregisterDataChangeListener(listener:DataChangeListener): void | DataChangeListener | Deregisters the listener for data changes.<br>**listener**: listener for data changes.     |
59
60
61## Description of DataChangeListener
62
63
64```ts
65interface DataChangeListener {
66    onDataReloaded(): void;
67    onDataAdded(index: number): void;
68    onDataMoved(from: number, to: number): void;
69    onDataDeleted(index: number): void;
70    onDataChanged(index: number): void;
71    onDataAdd(index: number): void;
72    onDataMove(from: number, to: number): void;
73    onDataDelete(index: number): void;
74    onDataChange(index: number): void;
75}
76```
77
78| Declaration                                                    | Parameter Type                              | Description                                                        |
79| ------------------------------------------------------------ | -------------------------------------- | ------------------------------------------------------------ |
80| onDataReloaded(): void                                  | -                                      | Invoked when all data is reloaded.                                  |
81| onDataAdd(index: number): void<sup>8+</sup>        | number                                 | Invoked when data is added to the position indicated by the specified index.<br>**index**: index of the position where data is added.|
82| onDataMove(from: number, to: number): void<sup>8+</sup> | from: number,<br>to: number | Invoked when data is moved.<br>**from**: original position of data; **to**: target position of data.<br>**NOTE**<br>The ID must remain unchanged before and after data movement. If the ID changes, APIs for deleting and adding data must be called.|
83| onDataDelete(index: number):void<sup>8+</sup>                | number                                 | Invoked when data is deleted from the position indicated by the specified index. LazyForEach will update the displayed content accordingly.<br>**index**: index of the position where data is deleted.<br>**NOTE**<br>Before **onDataDelete** is called, ensure that the corresponding data in **dataSource** has been deleted. Otherwise, undefined behavior will occur during page rendering.|
84| onDataChange(index: number): void<sup>8+</sup>     | number                                 | Invoked when data in the position indicated by the specified index is changed.<br>**index**: index of the position where data is changed.|
85| onDataAdded(index: number):void<sup>(deprecated)</sup>  | number                                 | Invoked when data is added to the position indicated by the specified index.<br>This API is deprecated since API version 8. You are advised to use **onDataAdd** instead.<br>**index**: index of the position where data is added.|
86| onDataMoved(from: number, to: number): void<sup>(deprecated)</sup> | from: number,<br>to: number | Invoked when data is moved.<br>This API is deprecated since API version 8. You are advised to use **onDataMove** instead.<br>**from**: original position of data; **to**: target position of data.<br>**NOTE**<br>The ID must remain unchanged before and after data movement. If the ID changes, APIs for deleting and adding data must be called.|
87| onDataDeleted(index: number):void<sup>(deprecated)</sup>     | number                                 | Invoked when data is deleted from the position indicated by the specified index. LazyForEach will update the displayed content accordingly.<br>This API is deprecated since API version 8. You are advised to use **onDataDelete** instead.<br>**index**: index of the position where data is deleted.|
88| onDataChanged(index: number): void<sup>(deprecated)</sup> | number                                 | Invoked when data in the position indicated by the specified index is changed.<br>This API is deprecated since API version 8. You are advised to use **onDataChange** instead.<br>**index**: listener for data changes.|
89
90
91## Restrictions
92
93- LazyForEach must be used in the container component. Only the [\<List>](../reference/arkui-ts/ts-container-list.md), [\<Grid>](../reference/arkui-ts/ts-container-grid.md), and [\<Swiper>](../reference/arkui-ts/ts-container-swiper.md) components support lazy loading (that is, only the visible part and a small amount of data before and after the visible part are loaded for caching). For other components, all data is loaded at the same time.
94
95- **LazyForEach** must create one and only one child component in each iteration.
96
97- The generated child components must be allowed in the parent container component of **LazyForEach**.
98
99- **LazyForEach** can be included in an **if/else** statement, and can also contain such a statement.
100
101- The ID generation function must generate a unique value for each piece of data. If the IDs are the same, the framework ignores the UI components with the same key value. As a result, these UI components cannot be displayed in the parent container.
102
103- **LazyForEach** must be updated using a **DataChangeListener** object. When the first parameter **dataSource** uses a state variable, a state variable change does not trigger the UI update of **LazyForEach**.
104
105- For better rendering performance, when the **onDataChange** API of the **DataChangeListener** object is used to update the UI, an ID different from the original one needs to be generated to trigger component re-rendering.
106
107- The call sequence of **itemGenerator** functions may be different from the order of the data items in the data source. Therefore, do not assume whether or when the **itemGenerator** and **keyGenerator** functions are executed. For example, the following example may not run properly:
108
109
110  ```ts
111  LazyForEach(dataSource,
112    item => Text(`${item.i}. item.data.label`),
113    item => item.data.id.toString())
114  ```
115
116
117## Example
118
119
120```ts
121// Basic implementation of IDataSource to handle data listener
122class BasicDataSource implements IDataSource {
123  private listeners: DataChangeListener[] = [];
124
125  public totalCount(): number {
126    return 0;
127  }
128
129  public getData(index: number): any {
130    return undefined;
131  }
132
133  registerDataChangeListener(listener: DataChangeListener): void {
134    if (this.listeners.indexOf(listener) < 0) {
135      console.info('add listener');
136      this.listeners.push(listener);
137    }
138  }
139
140  unregisterDataChangeListener(listener: DataChangeListener): void {
141    const pos = this.listeners.indexOf(listener);
142    if (pos >= 0) {
143      console.info('remove listener');
144      this.listeners.splice(pos, 1);
145    }
146  }
147
148  notifyDataReload(): void {
149    this.listeners.forEach(listener => {
150      listener.onDataReloaded();
151    })
152  }
153
154  notifyDataAdd(index: number): void {
155    this.listeners.forEach(listener => {
156      listener.onDataAdd(index);
157    })
158  }
159
160  notifyDataChange(index: number): void {
161    this.listeners.forEach(listener => {
162      listener.onDataChange(index);
163    })
164  }
165
166  notifyDataDelete(index: number): void {
167    this.listeners.forEach(listener => {
168      listener.onDataDelete(index);
169    })
170  }
171
172  notifyDataMove(from: number, to: number): void {
173    this.listeners.forEach(listener => {
174      listener.onDataMove(from, to);
175    })
176  }
177}
178
179class MyDataSource extends BasicDataSource {
180  private dataArray: string[] = [];
181
182  public totalCount(): number {
183    return this.dataArray.length;
184  }
185
186  public getData(index: number): any {
187    return this.dataArray[index];
188  }
189
190  public addData(index: number, data: string): void {
191    this.dataArray.splice(index, 0, data);
192    this.notifyDataAdd(index);
193  }
194
195  public pushData(data: string): void {
196    this.dataArray.push(data);
197    this.notifyDataAdd(this.dataArray.length - 1);
198  }
199}
200
201@Entry
202@Component
203struct MyComponent {
204  aboutToAppear() {
205    for (var i = 100; i >= 80; i--) {
206      this.data.pushData(`Hello ${i}`)
207    }
208  }
209
210  private data: MyDataSource = new MyDataSource();
211
212  build() {
213    List({ space: 3 }) {
214      LazyForEach(this.data, (item: string) => {
215        ListItem() {
216          Row() {
217            Text(item).fontSize(50)
218              .onAppear(() => {
219                console.info("appear:" + item)
220              })
221          }.margin({ left: 10, right: 10 })
222        }
223        .onClick(() => {
224          this.data.pushData(`Hello ${this.data.totalCount()}`);
225        })
226      }, item => item)
227    }.cachedCount(5)
228  }
229}
230```
231