• 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:&nbsp;any)&nbsp;=&gt;&nbsp;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:&nbsp;any)&nbsp;=&gt;&nbsp;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():&nbsp;number                | -                  | Obtains the total number of data records.                              |
56| getData(index:&nbsp;number):&nbsp;any    | number             | Obtains the data record corresponding to the specified index.<br>**index**: index of the data record to obtain.|
57| registerDataChangeListener(listener:DataChangeListener):&nbsp;void | DataChangeListener | Registers a listener for data changes.<br>**listener**: listener for data changes.     |
58| unregisterDataChangeListener(listener:DataChangeListener):&nbsp;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():&nbsp;void              | -                                      | Invoked when all data is reloaded.                           |
81| onDataAdded(index:&nbsp;number):void     | 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| onDataMoved(from:&nbsp;number,&nbsp;to:&nbsp;number):&nbsp;void | from:&nbsp;number,<br>to:&nbsp;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| onDataChanged(index:&nbsp;number):&nbsp;void | number                                 | Invoked when data in the position indicated by the specified index is changed.<br>**index**: listener for data changes.  |
84| onDataAdd(index:&nbsp;number):&nbsp;void | number                                 | Invoked when data is added to the position indicated by the specified index.<br>**index**: index of the position where data is added. |
85| onDataMove(from:&nbsp;number,&nbsp;to:&nbsp;number):&nbsp;void | from:&nbsp;number,<br>to:&nbsp;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.|
86| onDataChanged(index:&nbsp;number):&nbsp;void | number                                 | Invoked when data in the position indicated by the specified index is changed.<br>**index**: index of the position where data is changed.|
87
88
89## Restrictions
90
91- **LazyForEach** must be used in the container component. Only the **\<List>**, **\<Grid>**, and **\<Swiper>** 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.
92
93- **LazyForEach** must create one and only one child component in each iteration.
94
95- The generated child components must be allowed in the parent container component of **LazyForEach**.
96
97- **LazyForEach** can be included in an **if/else** statement, and can also contain such a statement.
98
99- 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.
100
101- **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**.
102
103- 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.
104
105- 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:
106
107
108  ```ts
109  LazyForEach(dataSource,
110    item => Text(`${item.i}. item.data.label`),
111    item => item.data.id.toString())
112  ```
113
114
115## Example
116
117
118```ts
119// Basic implementation of IDataSource to handle data listener
120class BasicDataSource implements IDataSource {
121  private listeners: DataChangeListener[] = [];
122
123  public totalCount(): number {
124    return 0;
125  }
126
127  public getData(index: number): any {
128    return undefined;
129  }
130
131  registerDataChangeListener(listener: DataChangeListener): void {
132    if (this.listeners.indexOf(listener) < 0) {
133      console.info('add listener');
134      this.listeners.push(listener);
135    }
136  }
137
138  unregisterDataChangeListener(listener: DataChangeListener): void {
139    const pos = this.listeners.indexOf(listener);
140    if (pos >= 0) {
141      console.info('remove listener');
142      this.listeners.splice(pos, 1);
143    }
144  }
145
146  notifyDataReload(): void {
147    this.listeners.forEach(listener => {
148      listener.onDataReloaded();
149    })
150  }
151
152  notifyDataAdd(index: number): void {
153    this.listeners.forEach(listener => {
154      listener.onDataAdd(index);
155    })
156  }
157
158  notifyDataChange(index: number): void {
159    this.listeners.forEach(listener => {
160      listener.onDataChange(index);
161    })
162  }
163
164  notifyDataDelete(index: number): void {
165    this.listeners.forEach(listener => {
166      listener.onDataDelete(index);
167    })
168  }
169
170  notifyDataMove(from: number, to: number): void {
171    this.listeners.forEach(listener => {
172      listener.onDataMove(from, to);
173    })
174  }
175}
176
177class MyDataSource extends BasicDataSource {
178  private dataArray: string[] = ['/path/image0', '/path/image1', '/path/image2', '/path/image3'];
179
180  public totalCount(): number {
181    return this.dataArray.length;
182  }
183
184  public getData(index: number): any {
185    return this.dataArray[index];
186  }
187
188  public addData(index: number, data: string): void {
189    this.dataArray.splice(index, 0, data);
190    this.notifyDataAdd(index);
191  }
192
193  public pushData(data: string): void {
194    this.dataArray.push(data);
195    this.notifyDataAdd(this.dataArray.length - 1);
196  }
197}
198
199@Entry
200@Component
201struct MyComponent {
202  private data: MyDataSource = new MyDataSource();
203
204  build() {
205    List({ space: 3 }) {
206      LazyForEach(this.data, (item: string) => {
207        ListItem() {
208          Row() {
209            Image(item).width('30%').height(50)
210            Text(item).fontSize(20).margin({ left: 10 })
211          }.margin({ left: 10, right: 10 })
212        }
213        .onClick(() => {
214          this.data.pushData('/path/image' + this.data.totalCount());
215        })
216      }, item => item)
217    }
218  }
219}
220```
221