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) => 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) => 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| onDataAdded(index: 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: number, to: number): void | 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| onDataChanged(index: number): void | number | Invoked when data in the position indicated by the specified index is changed.<br>**index**: listener for data changes. | 84| onDataAdd(index: 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. | 85| onDataMove(from: number, to: number): void | 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.| 86| onDataChanged(index: number): 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