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, index?: number) => void, // Child component generation function. 14 keyGenerator?: (item: any, index?: number) => string // Key generation function. 15): void 16``` 17 18**Parameters** 19 20 21| Name | Type | Mandatory| Description | 22| ------------- | ------------------------------------------------------ | ---- | ------------------------------------------------------------ | 23| dataSource | [IDataSource](#idatasource) | Yes | **LazyForEach** data source. You need to implement related APIs. | 24| itemGenerator | (item: any, index?:number) => void | Yes | Child component generation function, which generates a child component for each data item in the array.<br>**NOTE**<br>**item** indicates the current data item, and **index** indicates the index of the data item.<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**.| 25| keyGenerator | (item: any, index?:number) => 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>**item** indicates the current data item, and **index** indicates the index of the data item.<br>The ID generated for each data item in the data source must be unique.| 26 27## IDataSource 28 29```ts 30interface IDataSource { 31 totalCount(): number; // Obtain the total number of data items. 32 getData(index: number): Object; // Obtain the data item that matches the specified index. 33 registerDataChangeListener(listener: DataChangeListener): void; // Register a data change listener. 34 unregisterDataChangeListener(listener: DataChangeListener): void; // Deregister the data change listener. 35} 36``` 37 38| Declaration | Parameter Type | Description | 39| ------------------------------------------------------------ | ------------------------------------------------- | ----------------------------------------------------------- | 40| totalCount(): number | - | Obtains the total number of data items. | 41| getData(index: number): any | number | Obtains the data item that matches the specified index.<br>**index**: index of the data item to obtain.| 42| registerDataChangeListener(listener:[DataChangeListener](#datachangelistener)): void | [DataChangeListener](#datachangelistener) | Registers a listener for data changes.<br>**listener**: listener for data changes. | 43| unregisterDataChangeListener(listener:[DataChangeListener](#datachangelistener)): void | [DataChangeListener](#datachangelistener) | Deregisters the listener for data changes.<br>**listener**: listener for data changes. | 44 45## DataChangeListener 46 47```ts 48interface DataChangeListener { 49 onDataReloaded(): void; // Invoked after data is reloaded. 50 onDataAdded(index: number): void; // Invoked after data is added. 51 onDataMoved(from: number, to: number): void; // Invoked after data is moved. 52 onDataDeleted(index: number): void; // Invoked after data is deleted. 53 onDataChanged(index: number): void; // Invoked after data is changed. 54 onDataAdd(index: number): void; // Invoked after data is added. 55 onDataMove(from: number, to: number): void; // Invoked after data is moved. 56 onDataDelete(index: number): void; // Invoked after data is deleted. 57 onDataChange(index: number): void; // Invoked after data is changed. 58} 59``` 60 61| Declaration | Parameter Type | Description | 62| ------------------------------------------------------------ | -------------------------------------- | ------------------------------------------------------------ | 63| onDataReloaded(): void | - | Invoked when all data is reloaded.<br>For data items whose key remains unchanged, the original child component is used. For data items whose key changes, a new child component is created.| 64| 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.| 65| 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>Data is swapped between the **from** and **to** positions.<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.| 66| 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.| 67| 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.| 68| 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.| 69| 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>Data is swapped between the **from** and **to** positions.<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.| 70| 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.| 71| 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.| 72 73 74## Constraints 75 76- LazyForEach must be used in a container component. Only the [\<List>](../reference/apis-arkui/arkui-ts/ts-container-list.md), [\<Grid>](../reference/apis-arkui/arkui-ts/ts-container-grid.md), [\<Swiper>](../reference/apis-arkui/arkui-ts/ts-container-swiper.md), and [\<WaterFlow>](../reference/apis-arkui/arkui-ts/ts-container-waterflow.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 once. 77- **LazyForEach** must create one and only one child component in each iteration. 78- The generated child components must be allowed in the parent container component of **LazyForEach**. 79- **LazyForEach** can be included in an **if/else** statement, and can also contain such a statement. 80- 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. 81- **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**. 82- 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. 83 84## Key Generation Rules 85 86During **LazyForEach** rendering, the system generates a unique, persistent key for each item to identify the owing component. When the key changes, the ArkUI framework considers that the array element has been replaced or modified and creates a new component based on the new key. 87 88**LazyForEach** provides a parameter named **keyGenerator**, which is in effect a function through which you can customize key generation rules. If no **keyGenerator** function is defined, the ArkUI framework uses the default key generation function, that is, **(item: any, index: number) => { return viewId + '-' + index.toString(); }**, wherein **viewId** is generated during compiler conversion. The **viewId** values in the same **LazyForEach** component are the same. 89 90## Component Creation Rules 91 92After the key generation rules are determined, the **itemGenerator** function – the second parameter in **LazyForEach** – creates a component for each array item of the data source based on the rules. There are two cases for creating a component: [initial render](#initial-render) and [non initial render](#non-initial-render). 93 94### Initial Render 95 96- ### Generating Different Key Values 97 98When used for initial render, **LazyForEach** generates a unique key for each array item of the data source based on the key generation rules, and creates a component. 99 100```ts 101// Basic implementation of IDataSource to handle data listener 102class BasicDataSource implements IDataSource { 103 private listeners: DataChangeListener[] = []; 104 private originDataArray: string[] = []; 105 106 public totalCount(): number { 107 return 0; 108 } 109 110 public getData(index: number): string { 111 return this.originDataArray[index]; 112 } 113 114 // This method is called by the framework to add a listener to the LazyForEach data source. 115 registerDataChangeListener(listener: DataChangeListener): void { 116 if (this.listeners.indexOf(listener) < 0) { 117 console.info('add listener'); 118 this.listeners.push(listener); 119 } 120 } 121 122 // This method is called by the framework to remove the listener from the LazyForEach data source. 123 unregisterDataChangeListener(listener: DataChangeListener): void { 124 const pos = this.listeners.indexOf(listener); 125 if (pos >= 0) { 126 console.info('remove listener'); 127 this.listeners.splice(pos, 1); 128 } 129 } 130 131 // Notify LazyForEach that all child components need to be reloaded. 132 notifyDataReload(): void { 133 this.listeners.forEach(listener => { 134 listener.onDataReloaded(); 135 }) 136 } 137 138 // Notify LazyForEach that a child component needs to be added for the data item with the specified index. 139 notifyDataAdd(index: number): void { 140 this.listeners.forEach(listener => { 141 listener.onDataAdd(index); 142 }) 143 } 144 145 // Notify LazyForEach that the data item with the specified index has changed and the child component needs to be rebuilt. 146 notifyDataChange(index: number): void { 147 this.listeners.forEach(listener => { 148 listener.onDataChange(index); 149 }) 150 } 151 152 // Notify LazyForEach that the child component that matches the specified index needs to be deleted. 153 notifyDataDelete(index: number): void { 154 this.listeners.forEach(listener => { 155 listener.onDataDelete(index); 156 }) 157 } 158 159 // Notify LazyForEach that data needs to be swapped between the from and to positions. 160 notifyDataMove(from: number, to: number): void { 161 this.listeners.forEach(listener => { 162 listener.onDataMove(from, to); 163 }) 164 } 165} 166 167class MyDataSource extends BasicDataSource { 168 private dataArray: string[] = []; 169 170 public totalCount(): number { 171 return this.dataArray.length; 172 } 173 174 public getData(index: number): string { 175 return this.dataArray[index]; 176 } 177 178 public addData(index: number, data: string): void { 179 this.dataArray.splice(index, 0, data); 180 this.notifyDataAdd(index); 181 } 182 183 public pushData(data: string): void { 184 this.dataArray.push(data); 185 this.notifyDataAdd(this.dataArray.length - 1); 186 } 187} 188 189@Entry 190@Component 191struct MyComponent { 192 private data: MyDataSource = new MyDataSource(); 193 194 aboutToAppear() { 195 for (let i = 0; i <= 20; i++) { 196 this.data.pushData(`Hello ${i}`) 197 } 198 } 199 200 build() { 201 List({ space: 3 }) { 202 LazyForEach(this.data, (item: string) => { 203 ListItem() { 204 Row() { 205 Text(item).fontSize(50) 206 .onAppear(() => { 207 console.info("appear:" + item) 208 }) 209 }.margin({ left: 10, right: 10 }) 210 } 211 }, (item: string) => item) 212 }.cachedCount(5) 213 } 214} 215``` 216 217In the preceding code snippets, the key generation rule is the return value **item** of the **keyGenerator** function. During **LazyForEach** rendering, keys (from **Hello 0**, **Hello 1**, through to **Hello 20**) are generated in sequence for data source array items, and corresponding list items are created and rendered to the UI. 218 219The figure below shows the effect. 220 221**Figure 1** Initial render of LazyForEach 222 223 224- ### Incorrect Rendering When Keys Are the Same 225 226When the keys generated for different data items are the same, the behavior of the framework is unpredictable. For example, in the following code, the keys of the data items rendered by **LazyForEach** are the same. During the swipe process, **LazyForEach** preloads child components for the current page. Because the new child component and the destroyed component have the same key, the framework may incorrectly obtain the cache. As a result, the child component rendering is abnormal. 227 228 ```ts 229class BasicDataSource implements IDataSource { 230 private listeners: DataChangeListener[] = []; 231 private originDataArray: string[] = []; 232 233 public totalCount(): number { 234 return 0; 235 } 236 237 public getData(index: number): string { 238 return this.originDataArray[index]; 239 } 240 241 registerDataChangeListener(listener: DataChangeListener): void { 242 if (this.listeners.indexOf(listener) < 0) { 243 console.info('add listener'); 244 this.listeners.push(listener); 245 } 246 } 247 248 unregisterDataChangeListener(listener: DataChangeListener): void { 249 const pos = this.listeners.indexOf(listener); 250 if (pos >= 0) { 251 console.info('remove listener'); 252 this.listeners.splice(pos, 1); 253 } 254 } 255 256 notifyDataReload(): void { 257 this.listeners.forEach(listener => { 258 listener.onDataReloaded(); 259 }) 260 } 261 262 notifyDataAdd(index: number): void { 263 this.listeners.forEach(listener => { 264 listener.onDataAdd(index); 265 }) 266 } 267 268 notifyDataChange(index: number): void { 269 this.listeners.forEach(listener => { 270 listener.onDataChange(index); 271 }) 272 } 273 274 notifyDataDelete(index: number): void { 275 this.listeners.forEach(listener => { 276 listener.onDataDelete(index); 277 }) 278 } 279 280 notifyDataMove(from: number, to: number): void { 281 this.listeners.forEach(listener => { 282 listener.onDataMove(from, to); 283 }) 284 } 285} 286 287class MyDataSource extends BasicDataSource { 288 private dataArray: string[] = []; 289 290 public totalCount(): number { 291 return this.dataArray.length; 292 } 293 294 public getData(index: number): string { 295 return this.dataArray[index]; 296 } 297 298 public addData(index: number, data: string): void { 299 this.dataArray.splice(index, 0, data); 300 this.notifyDataAdd(index); 301 } 302 303 public pushData(data: string): void { 304 this.dataArray.push(data); 305 this.notifyDataAdd(this.dataArray.length - 1); 306 } 307} 308 309@Entry 310@Component 311struct MyComponent { 312 private data: MyDataSource = new MyDataSource(); 313 314 aboutToAppear() { 315 for (let i = 0; i <= 20; i++) { 316 this.data.pushData(`Hello ${i}`) 317 } 318 } 319 320 build() { 321 List({ space: 3 }) { 322 LazyForEach(this.data, (item: string) => { 323 ListItem() { 324 Row() { 325 Text(item).fontSize(50) 326 .onAppear(() => { 327 console.info("appear:" + item) 328 }) 329 }.margin({ left: 10, right: 10 }) 330 } 331 }, (item: string) => 'same key') 332 }.cachedCount(5) 333 } 334} 335 ``` 336 337The figure below shows the effect. You can see that **Hello 0** is incorrectly rendered as **Hello 13** during the swiping. 338 339**Figure 2** LazyForEach rendering when keys are the same 340 341 342### Non Initial Render 343 344When the **LazyForEach** data source changes and a re-render is required, call a listener API based on the data source change to notify **LazyForEach**. Below are some use cases. 345 346- ### Adding Data 347 348```ts 349class BasicDataSource implements IDataSource { 350 private listeners: DataChangeListener[] = []; 351 private originDataArray: string[] = []; 352 353 public totalCount(): number { 354 return 0; 355 } 356 357 public getData(index: number): string { 358 return this.originDataArray[index]; 359 } 360 361 registerDataChangeListener(listener: DataChangeListener): void { 362 if (this.listeners.indexOf(listener) < 0) { 363 console.info('add listener'); 364 this.listeners.push(listener); 365 } 366 } 367 368 unregisterDataChangeListener(listener: DataChangeListener): void { 369 const pos = this.listeners.indexOf(listener); 370 if (pos >= 0) { 371 console.info('remove listener'); 372 this.listeners.splice(pos, 1); 373 } 374 } 375 376 notifyDataReload(): void { 377 this.listeners.forEach(listener => { 378 listener.onDataReloaded(); 379 }) 380 } 381 382 notifyDataAdd(index: number): void { 383 this.listeners.forEach(listener => { 384 listener.onDataAdd(index); 385 }) 386 } 387 388 notifyDataChange(index: number): void { 389 this.listeners.forEach(listener => { 390 listener.onDataChange(index); 391 }) 392 } 393 394 notifyDataDelete(index: number): void { 395 this.listeners.forEach(listener => { 396 listener.onDataDelete(index); 397 }) 398 } 399 400 notifyDataMove(from: number, to: number): void { 401 this.listeners.forEach(listener => { 402 listener.onDataMove(from, to); 403 }) 404 } 405} 406 407class MyDataSource extends BasicDataSource { 408 private dataArray: string[] = []; 409 410 public totalCount(): number { 411 return this.dataArray.length; 412 } 413 414 public getData(index: number): string { 415 return this.dataArray[index]; 416 } 417 418 public addData(index: number, data: string): void { 419 this.dataArray.splice(index, 0, data); 420 this.notifyDataAdd(index); 421 } 422 423 public pushData(data: string): void { 424 this.dataArray.push(data); 425 this.notifyDataAdd(this.dataArray.length - 1); 426 } 427} 428 429@Entry 430@Component 431struct MyComponent { 432 private data: MyDataSource = new MyDataSource(); 433 434 aboutToAppear() { 435 for (let i = 0; i <= 20; i++) { 436 this.data.pushData(`Hello ${i}`) 437 } 438 } 439 440 build() { 441 List({ space: 3 }) { 442 LazyForEach(this.data, (item: string) => { 443 ListItem() { 444 Row() { 445 Text(item).fontSize(50) 446 .onAppear(() => { 447 console.info("appear:" + item) 448 }) 449 }.margin({ left: 10, right: 10 }) 450 } 451 .onClick(() => { 452 // Click to add a child component. 453 this.data.pushData(`Hello ${this.data.totalCount()}`); 454 }) 455 }, (item: string) => item) 456 }.cachedCount(5) 457 } 458} 459``` 460 461When the child component of **LazyForEach** is clicked, the **pushData** method of the data source is called first. This method adds data to the end of the data source and then calls the **notifyDataAdd** method. In the **notifyDataAdd** method, the **listener.onDataAdd** method is called to notify **LazyForEach** that data is added, and LazyForEach creates a child component at the position indicated by the specified index. 462 463The figure below shows the effect. 464 465**Figure 3** Adding data to LazyForEach 466 467 468- ### Deleting Data 469 470```ts 471class BasicDataSource implements IDataSource { 472 private listeners: DataChangeListener[] = []; 473 private originDataArray: string[] = []; 474 475 public totalCount(): number { 476 return 0; 477 } 478 479 public getData(index: number): string { 480 return this.originDataArray[index]; 481 } 482 483 registerDataChangeListener(listener: DataChangeListener): void { 484 if (this.listeners.indexOf(listener) < 0) { 485 console.info('add listener'); 486 this.listeners.push(listener); 487 } 488 } 489 490 unregisterDataChangeListener(listener: DataChangeListener): void { 491 const pos = this.listeners.indexOf(listener); 492 if (pos >= 0) { 493 console.info('remove listener'); 494 this.listeners.splice(pos, 1); 495 } 496 } 497 498 notifyDataReload(): void { 499 this.listeners.forEach(listener => { 500 listener.onDataReloaded(); 501 }) 502 } 503 504 notifyDataAdd(index: number): void { 505 this.listeners.forEach(listener => { 506 listener.onDataAdd(index); 507 }) 508 } 509 510 notifyDataChange(index: number): void { 511 this.listeners.forEach(listener => { 512 listener.onDataChange(index); 513 }) 514 } 515 516 notifyDataDelete(index: number): void { 517 this.listeners.forEach(listener => { 518 listener.onDataDelete(index); 519 }) 520 } 521 522 notifyDataMove(from: number, to: number): void { 523 this.listeners.forEach(listener => { 524 listener.onDataMove(from, to); 525 }) 526 } 527} 528 529class MyDataSource extends BasicDataSource { 530 dataArray: string[] = []; 531 532 public totalCount(): number { 533 return this.dataArray.length; 534 } 535 536 public getData(index: number): string { 537 return this.dataArray[index]; 538 } 539 540 public addData(index: number, data: string): void { 541 this.dataArray.splice(index, 0, data); 542 this.notifyDataAdd(index); 543 } 544 545 public pushData(data: string): void { 546 this.dataArray.push(data); 547 this.notifyDataAdd(this.dataArray.length - 1); 548 } 549 550 public deleteData(index: number): void { 551 this.dataArray.splice(index, 1); 552 this.notifyDataDelete(index); 553 } 554} 555 556@Entry 557@Component 558struct MyComponent { 559 private data: MyDataSource = new MyDataSource(); 560 561 aboutToAppear() { 562 for (let i = 0; i <= 20; i++) { 563 this.data.pushData(`Hello ${i}`) 564 } 565 } 566 567 build() { 568 List({ space: 3 }) { 569 LazyForEach(this.data, (item: string, index: number) => { 570 ListItem() { 571 Row() { 572 Text(item).fontSize(50) 573 .onAppear(() => { 574 console.info("appear:" + item) 575 }) 576 }.margin({ left: 10, right: 10 }) 577 } 578 .onClick(() => { 579 // Click to delete a child component. 580 this.data.deleteData(this.data.dataArray.indexOf(item)); 581 }) 582 }, (item: string) => item) 583 }.cachedCount(5) 584 } 585} 586``` 587 588When the child component of **LazyForEach** is clicked, the **deleteData** method of the data source is called first. This method deletes data that matches the specified index from the data source and then calls the **notifyDataDelete** method. In the **notifyDataDelete** method, the **listener.onDataDelete** method is called to notify **LazyForEach** that data is deleted, and **LazyForEach** deletes the child component at the position indicated by the specified index. 589 590The figure below shows the effect. 591 592**Figure 4** Deleting data from LazyForEach 593 594 595- ### Swapping Data 596 597```ts 598class BasicDataSource implements IDataSource { 599 private listeners: DataChangeListener[] = []; 600 private originDataArray: string[] = []; 601 602 public totalCount(): number { 603 return 0; 604 } 605 606 public getData(index: number): string { 607 return this.originDataArray[index]; 608 } 609 610 registerDataChangeListener(listener: DataChangeListener): void { 611 if (this.listeners.indexOf(listener) < 0) { 612 console.info('add listener'); 613 this.listeners.push(listener); 614 } 615 } 616 617 unregisterDataChangeListener(listener: DataChangeListener): void { 618 const pos = this.listeners.indexOf(listener); 619 if (pos >= 0) { 620 console.info('remove listener'); 621 this.listeners.splice(pos, 1); 622 } 623 } 624 625 notifyDataReload(): void { 626 this.listeners.forEach(listener => { 627 listener.onDataReloaded(); 628 }) 629 } 630 631 notifyDataAdd(index: number): void { 632 this.listeners.forEach(listener => { 633 listener.onDataAdd(index); 634 }) 635 } 636 637 notifyDataChange(index: number): void { 638 this.listeners.forEach(listener => { 639 listener.onDataChange(index); 640 }) 641 } 642 643 notifyDataDelete(index: number): void { 644 this.listeners.forEach(listener => { 645 listener.onDataDelete(index); 646 }) 647 } 648 649 notifyDataMove(from: number, to: number): void { 650 this.listeners.forEach(listener => { 651 listener.onDataMove(from, to); 652 }) 653 } 654} 655 656class MyDataSource extends BasicDataSource { 657 dataArray: string[] = []; 658 659 public totalCount(): number { 660 return this.dataArray.length; 661 } 662 663 public getData(index: number): string { 664 return this.dataArray[index]; 665 } 666 667 public addData(index: number, data: string): void { 668 this.dataArray.splice(index, 0, data); 669 this.notifyDataAdd(index); 670 } 671 672 public pushData(data: string): void { 673 this.dataArray.push(data); 674 this.notifyDataAdd(this.dataArray.length - 1); 675 } 676 677 public deleteData(index: number): void { 678 this.dataArray.splice(index, 1); 679 this.notifyDataDelete(index); 680 } 681 682 public moveData(from: number, to: number): void { 683 let temp: string = this.dataArray[from]; 684 this.dataArray[from] = this.dataArray[to]; 685 this.dataArray[to] = temp; 686 this.notifyDataMove(from, to); 687 } 688} 689 690@Entry 691@Component 692struct MyComponent { 693 private moved: number[] = []; 694 private data: MyDataSource = new MyDataSource(); 695 696 aboutToAppear() { 697 for (let i = 0; i <= 20; i++) { 698 this.data.pushData(`Hello ${i}`) 699 } 700 } 701 702 build() { 703 List({ space: 3 }) { 704 LazyForEach(this.data, (item: string, index: number) => { 705 ListItem() { 706 Row() { 707 Text(item).fontSize(50) 708 .onAppear(() => { 709 console.info("appear:" + item) 710 }) 711 }.margin({ left: 10, right: 10 }) 712 } 713 .onClick(() => { 714 this.moved.push(this.data.dataArray.indexOf(item)); 715 if (this.moved.length === 2) { 716 // Click to move a child component. 717 this.data.moveData(this.moved[0], this.moved[1]); 718 this.moved = []; 719 } 720 }) 721 }, (item: string) => item) 722 }.cachedCount(5) 723 } 724} 725``` 726 727When a child component of **LazyForEach** is clicked, the index of the data to be moved is stored in the **moved** member variable. When another child component of **LazyForEach** is clicked, the first child component clicked is moved here. The **moveData** method of the data source is called to move the data from the original location to the expected location, after which the **notifyDatMove** method is called. In the **notifyDataMove** method, the **listener.onDataMove** method is called to notify **LazyForEach** that data needs to be moved.** LazyForEach** then swaps data between the **from** and **to** positions. 728 729The figure below shows the effect. 730 731**Figure 5** Swapping data in LazyForEach 732 733 734- ### Changing a Data Item 735 736```ts 737class BasicDataSource implements IDataSource { 738 private listeners: DataChangeListener[] = []; 739 private originDataArray: string[] = []; 740 741 public totalCount(): number { 742 return 0; 743 } 744 745 public getData(index: number): string { 746 return this.originDataArray[index]; 747 } 748 749 registerDataChangeListener(listener: DataChangeListener): void { 750 if (this.listeners.indexOf(listener) < 0) { 751 console.info('add listener'); 752 this.listeners.push(listener); 753 } 754 } 755 756 unregisterDataChangeListener(listener: DataChangeListener): void { 757 const pos = this.listeners.indexOf(listener); 758 if (pos >= 0) { 759 console.info('remove listener'); 760 this.listeners.splice(pos, 1); 761 } 762 } 763 764 notifyDataReload(): void { 765 this.listeners.forEach(listener => { 766 listener.onDataReloaded(); 767 }) 768 } 769 770 notifyDataAdd(index: number): void { 771 this.listeners.forEach(listener => { 772 listener.onDataAdd(index); 773 }) 774 } 775 776 notifyDataChange(index: number): void { 777 this.listeners.forEach(listener => { 778 listener.onDataChange(index); 779 }) 780 } 781 782 notifyDataDelete(index: number): void { 783 this.listeners.forEach(listener => { 784 listener.onDataDelete(index); 785 }) 786 } 787 788 notifyDataMove(from: number, to: number): void { 789 this.listeners.forEach(listener => { 790 listener.onDataMove(from, to); 791 }) 792 } 793} 794 795class MyDataSource extends BasicDataSource { 796 private dataArray: string[] = []; 797 798 public totalCount(): number { 799 return this.dataArray.length; 800 } 801 802 public getData(index: number): string { 803 return this.dataArray[index]; 804 } 805 806 public addData(index: number, data: string): void { 807 this.dataArray.splice(index, 0, data); 808 this.notifyDataAdd(index); 809 } 810 811 public pushData(data: string): void { 812 this.dataArray.push(data); 813 this.notifyDataAdd(this.dataArray.length - 1); 814 } 815 816 public deleteData(index: number): void { 817 this.dataArray.splice(index, 1); 818 this.notifyDataDelete(index); 819 } 820 821 public changeData(index: number, data: string): void { 822 this.dataArray.splice(index, 1, data); 823 this.notifyDataChange(index); 824 } 825} 826 827@Entry 828@Component 829struct MyComponent { 830 private moved: number[] = []; 831 private data: MyDataSource = new MyDataSource(); 832 833 aboutToAppear() { 834 for (let i = 0; i <= 20; i++) { 835 this.data.pushData(`Hello ${i}`) 836 } 837 } 838 839 840 build() { 841 List({ space: 3 }) { 842 LazyForEach(this.data, (item: string, index: number) => { 843 ListItem() { 844 Row() { 845 Text(item).fontSize(50) 846 .onAppear(() => { 847 console.info("appear:" + item) 848 }) 849 }.margin({ left: 10, right: 10 }) 850 } 851 .onClick(() => { 852 this.data.changeData(index, item + '00'); 853 }) 854 }, (item: string) => item) 855 }.cachedCount(5) 856 } 857} 858``` 859 860When the child component of **LazyForEach** is clicked, the data is changed first, and then the **changeData** method of the data source is called. In this method, the **notifyDataChange** method is called. In the **notifyDataChange** method, the **listener.onDataChange** method is called to notify **LazyForEach** of data changes. **LazyForEach** then rebuilds the child component that matches the specified index. 861 862The figure below shows the effect. 863 864**Figure 6** Changing a data item in LazyForEach 865 866 867- ### Changing Multiple Data Items 868 869```ts 870class BasicDataSource implements IDataSource { 871 private listeners: DataChangeListener[] = []; 872 private originDataArray: string[] = []; 873 874 public totalCount(): number { 875 return 0; 876 } 877 878 public getData(index: number): string { 879 return this.originDataArray[index]; 880 } 881 882 registerDataChangeListener(listener: DataChangeListener): void { 883 if (this.listeners.indexOf(listener) < 0) { 884 console.info('add listener'); 885 this.listeners.push(listener); 886 } 887 } 888 889 unregisterDataChangeListener(listener: DataChangeListener): void { 890 const pos = this.listeners.indexOf(listener); 891 if (pos >= 0) { 892 console.info('remove listener'); 893 this.listeners.splice(pos, 1); 894 } 895 } 896 897 notifyDataReload(): void { 898 this.listeners.forEach(listener => { 899 listener.onDataReloaded(); 900 }) 901 } 902 903 notifyDataAdd(index: number): void { 904 this.listeners.forEach(listener => { 905 listener.onDataAdd(index); 906 }) 907 } 908 909 notifyDataChange(index: number): void { 910 this.listeners.forEach(listener => { 911 listener.onDataChange(index); 912 }) 913 } 914 915 notifyDataDelete(index: number): void { 916 this.listeners.forEach(listener => { 917 listener.onDataDelete(index); 918 }) 919 } 920 921 notifyDataMove(from: number, to: number): void { 922 this.listeners.forEach(listener => { 923 listener.onDataMove(from, to); 924 }) 925 } 926} 927 928class MyDataSource extends BasicDataSource { 929 private dataArray: string[] = []; 930 931 public totalCount(): number { 932 return this.dataArray.length; 933 } 934 935 public getData(index: number): string { 936 return this.dataArray[index]; 937 } 938 939 public addData(index: number, data: string): void { 940 this.dataArray.splice(index, 0, data); 941 this.notifyDataAdd(index); 942 } 943 944 public pushData(data: string): void { 945 this.dataArray.push(data); 946 this.notifyDataAdd(this.dataArray.length - 1); 947 } 948 949 public deleteData(index: number): void { 950 this.dataArray.splice(index, 1); 951 this.notifyDataDelete(index); 952 } 953 954 public changeData(index: number): void { 955 this.notifyDataChange(index); 956 } 957 958 public reloadData(): void { 959 this.notifyDataReload(); 960 } 961 962 public modifyAllData(): void { 963 this.dataArray = this.dataArray.map((item: string) => { 964 return item + '0'; 965 }) 966 } 967} 968 969@Entry 970@Component 971struct MyComponent { 972 private moved: number[] = []; 973 private data: MyDataSource = new MyDataSource(); 974 975 aboutToAppear() { 976 for (let i = 0; i <= 20; i++) { 977 this.data.pushData(`Hello ${i}`) 978 } 979 } 980 981 build() { 982 List({ space: 3 }) { 983 LazyForEach(this.data, (item: string, index: number) => { 984 ListItem() { 985 Row() { 986 Text(item).fontSize(50) 987 .onAppear(() => { 988 console.info("appear:" + item) 989 }) 990 }.margin({ left: 10, right: 10 }) 991 } 992 .onClick(() => { 993 this.data.modifyAllData(); 994 this.data.reloadData(); 995 }) 996 }, (item: string) => item) 997 }.cachedCount(5) 998 } 999} 1000``` 1001 1002When a child component of **LazyForEach** is clicked, the **modifyAllData** method of the data source is called to change all data items, and then the **reloadData** method of the data source is called. In this method, the **notifyDataReload** method is called. In the **notifyDataReload** method, the **listener.onDataReloaded** method is called to notify **LazyForEach** that all subnodes need to be rebuilt. **LazyForEach** compares the keys of all original data items with those of all new data items on a one-by-one basis. If the keys are the same, the cache is used. If the keys are different, the child component is rebuilt. 1003 1004The figure below shows the effect. 1005 1006**Figure 7** Changing multiple data items in LazyForEach 1007 1008 1009- ### Changing Data Subproperties 1010 1011When **LazyForEach** is used for UI re-renders, a child component needs to be destroyed and rebuilt when the data item changes. This may result in low re-render performance when the child component structure is complex. This is where @Observed and @ObjectLink come into picture. By providing in-depth observation, @Observed and @ObjectLink enable precise re-renders of only components that use the changed properties. You can select a re-render mode that better suits your needs. 1012 1013```ts 1014class BasicDataSource implements IDataSource { 1015 private listeners: DataChangeListener[] = []; 1016 private originDataArray: StringData[] = []; 1017 1018 public totalCount(): number { 1019 return 0; 1020 } 1021 1022 public getData(index: number): StringData { 1023 return this.originDataArray[index]; 1024 } 1025 1026 registerDataChangeListener(listener: DataChangeListener): void { 1027 if (this.listeners.indexOf(listener) < 0) { 1028 console.info('add listener'); 1029 this.listeners.push(listener); 1030 } 1031 } 1032 1033 unregisterDataChangeListener(listener: DataChangeListener): void { 1034 const pos = this.listeners.indexOf(listener); 1035 if (pos >= 0) { 1036 console.info('remove listener'); 1037 this.listeners.splice(pos, 1); 1038 } 1039 } 1040 1041 notifyDataReload(): void { 1042 this.listeners.forEach(listener => { 1043 listener.onDataReloaded(); 1044 }) 1045 } 1046 1047 notifyDataAdd(index: number): void { 1048 this.listeners.forEach(listener => { 1049 listener.onDataAdd(index); 1050 }) 1051 } 1052 1053 notifyDataChange(index: number): void { 1054 this.listeners.forEach(listener => { 1055 listener.onDataChange(index); 1056 }) 1057 } 1058 1059 notifyDataDelete(index: number): void { 1060 this.listeners.forEach(listener => { 1061 listener.onDataDelete(index); 1062 }) 1063 } 1064 1065 notifyDataMove(from: number, to: number): void { 1066 this.listeners.forEach(listener => { 1067 listener.onDataMove(from, to); 1068 }) 1069 } 1070} 1071 1072class MyDataSource extends BasicDataSource { 1073 private dataArray: StringData[] = []; 1074 1075 public totalCount(): number { 1076 return this.dataArray.length; 1077 } 1078 1079 public getData(index: number): StringData { 1080 return this.dataArray[index]; 1081 } 1082 1083 public addData(index: number, data: StringData): void { 1084 this.dataArray.splice(index, 0, data); 1085 this.notifyDataAdd(index); 1086 } 1087 1088 public pushData(data: StringData): void { 1089 this.dataArray.push(data); 1090 this.notifyDataAdd(this.dataArray.length - 1); 1091 } 1092} 1093 1094@Observed 1095class StringData { 1096 message: string; 1097 constructor(message: string) { 1098 this.message = message; 1099 } 1100} 1101 1102@Entry 1103@Component 1104struct MyComponent { 1105 private moved: number[] = []; 1106 @State data: MyDataSource = new MyDataSource(); 1107 1108 aboutToAppear() { 1109 for (let i = 0; i <= 20; i++) { 1110 this.data.pushData(new StringData(`Hello ${i}`)); 1111 } 1112 } 1113 1114 build() { 1115 List({ space: 3 }) { 1116 LazyForEach(this.data, (item: StringData, index: number) => { 1117 ListItem() { 1118 ChildComponent({data: item}) 1119 } 1120 .onClick(() => { 1121 item.message += '0'; 1122 }) 1123 }, (item: StringData, index: number) => index.toString()) 1124 }.cachedCount(5) 1125 } 1126} 1127 1128@Component 1129struct ChildComponent { 1130 @ObjectLink data: StringData 1131 build() { 1132 Row() { 1133 Text(this.data.message).fontSize(50) 1134 .onAppear(() => { 1135 console.info("appear:" + this.data.message) 1136 }) 1137 }.margin({ left: 10, right: 10 }) 1138 } 1139} 1140``` 1141 1142When the child component of **LazyForEach** is clicked, **item.message** is changed. As re-rendering depends on the listening of the @ObjectLink decorated member variable of **ChildComponent** on its subproperties. In this case, the framework only re-renders **Text(this.data.message)** and does not rebuild the entire **ListItem** child component. 1143 1144**Figure 8** Changing data subproperties in LazyForEach 1145 1146 1147## FAQs 1148 1149- ### Unexpected Rendering Result 1150 1151 ```ts 1152 class BasicDataSource implements IDataSource { 1153 private listeners: DataChangeListener[] = []; 1154 private originDataArray: string[] = []; 1155 1156 public totalCount(): number { 1157 return 0; 1158 } 1159 1160 public getData(index: number): string { 1161 return this.originDataArray[index]; 1162 } 1163 1164 registerDataChangeListener(listener: DataChangeListener): void { 1165 if (this.listeners.indexOf(listener) < 0) { 1166 console.info('add listener'); 1167 this.listeners.push(listener); 1168 } 1169 } 1170 1171 unregisterDataChangeListener(listener: DataChangeListener): void { 1172 const pos = this.listeners.indexOf(listener); 1173 if (pos >= 0) { 1174 console.info('remove listener'); 1175 this.listeners.splice(pos, 1); 1176 } 1177 } 1178 1179 notifyDataReload(): void { 1180 this.listeners.forEach(listener => { 1181 listener.onDataReloaded(); 1182 }) 1183 } 1184 1185 notifyDataAdd(index: number): void { 1186 this.listeners.forEach(listener => { 1187 listener.onDataAdd(index); 1188 }) 1189 } 1190 1191 notifyDataChange(index: number): void { 1192 this.listeners.forEach(listener => { 1193 listener.onDataChange(index); 1194 }) 1195 } 1196 1197 notifyDataDelete(index: number): void { 1198 this.listeners.forEach(listener => { 1199 listener.onDataDelete(index); 1200 }) 1201 } 1202 1203 notifyDataMove(from: number, to: number): void { 1204 this.listeners.forEach(listener => { 1205 listener.onDataMove(from, to); 1206 }) 1207 } 1208 } 1209 1210 class MyDataSource extends BasicDataSource { 1211 private dataArray: string[] = []; 1212 1213 public totalCount(): number { 1214 return this.dataArray.length; 1215 } 1216 1217 public getData(index: number): string { 1218 return this.dataArray[index]; 1219 } 1220 1221 public addData(index: number, data: string): void { 1222 this.dataArray.splice(index, 0, data); 1223 this.notifyDataAdd(index); 1224 } 1225 1226 public pushData(data: string): void { 1227 this.dataArray.push(data); 1228 this.notifyDataAdd(this.dataArray.length - 1); 1229 } 1230 1231 public deleteData(index: number): void { 1232 this.dataArray.splice(index, 1); 1233 this.notifyDataDelete(index); 1234 } 1235 } 1236 1237 @Entry 1238 @Component 1239 struct MyComponent { 1240 private data: MyDataSource = new MyDataSource(); 1241 1242 aboutToAppear() { 1243 for (let i = 0; i <= 20; i++) { 1244 this.data.pushData(`Hello ${i}`) 1245 } 1246 } 1247 1248 build() { 1249 List({ space: 3 }) { 1250 LazyForEach(this.data, (item: string, index: number) => { 1251 ListItem() { 1252 Row() { 1253 Text(item).fontSize(50) 1254 .onAppear(() => { 1255 console.info("appear:" + item) 1256 }) 1257 }.margin({ left: 10, right: 10 }) 1258 } 1259 .onClick(() => { 1260 // Click to delete a child component. 1261 this.data.deleteData(index); 1262 }) 1263 }, (item: string) => item) 1264 }.cachedCount(5) 1265 } 1266 } 1267 ``` 1268 1269 **Figure 9** Unexpected data deletion by LazyForEach 1270  1271 1272 When child components are clicked to be deleted, there may be cases where the deleted child component is not the one clicked. If this is the case, the indexes of data items are not updated correctly. In normal cases, after a child component is deleted, all data items following the data item of the child component should have their index decreased by 1. If these data items still use the original indexes, the indexes in **itemGenerator** do not change, resulting in the unexpected rendering result. 1273 1274 The following shows the code snippet after optimization: 1275 1276 ```ts 1277 class BasicDataSource implements IDataSource { 1278 private listeners: DataChangeListener[] = []; 1279 private originDataArray: string[] = []; 1280 1281 public totalCount(): number { 1282 return 0; 1283 } 1284 1285 public getData(index: number): string { 1286 return this.originDataArray[index]; 1287 } 1288 1289 registerDataChangeListener(listener: DataChangeListener): void { 1290 if (this.listeners.indexOf(listener) < 0) { 1291 console.info('add listener'); 1292 this.listeners.push(listener); 1293 } 1294 } 1295 1296 unregisterDataChangeListener(listener: DataChangeListener): void { 1297 const pos = this.listeners.indexOf(listener); 1298 if (pos >= 0) { 1299 console.info('remove listener'); 1300 this.listeners.splice(pos, 1); 1301 } 1302 } 1303 1304 notifyDataReload(): void { 1305 this.listeners.forEach(listener => { 1306 listener.onDataReloaded(); 1307 }) 1308 } 1309 1310 notifyDataAdd(index: number): void { 1311 this.listeners.forEach(listener => { 1312 listener.onDataAdd(index); 1313 }) 1314 } 1315 1316 notifyDataChange(index: number): void { 1317 this.listeners.forEach(listener => { 1318 listener.onDataChange(index); 1319 }) 1320 } 1321 1322 notifyDataDelete(index: number): void { 1323 this.listeners.forEach(listener => { 1324 listener.onDataDelete(index); 1325 }) 1326 } 1327 1328 notifyDataMove(from: number, to: number): void { 1329 this.listeners.forEach(listener => { 1330 listener.onDataMove(from, to); 1331 }) 1332 } 1333 } 1334 1335 class MyDataSource extends BasicDataSource { 1336 private dataArray: string[] = []; 1337 1338 public totalCount(): number { 1339 return this.dataArray.length; 1340 } 1341 1342 public getData(index: number): string { 1343 return this.dataArray[index]; 1344 } 1345 1346 public addData(index: number, data: string): void { 1347 this.dataArray.splice(index, 0, data); 1348 this.notifyDataAdd(index); 1349 } 1350 1351 public pushData(data: string): void { 1352 this.dataArray.push(data); 1353 this.notifyDataAdd(this.dataArray.length - 1); 1354 } 1355 1356 public deleteData(index: number): void { 1357 this.dataArray.splice(index, 1); 1358 this.notifyDataDelete(index); 1359 } 1360 1361 public reloadData(): void { 1362 this.notifyDataReload(); 1363 } 1364 } 1365 1366 @Entry 1367 @Component 1368 struct MyComponent { 1369 private data: MyDataSource = new MyDataSource(); 1370 1371 aboutToAppear() { 1372 for (let i = 0; i <= 20; i++) { 1373 this.data.pushData(`Hello ${i}`) 1374 } 1375 } 1376 1377 build() { 1378 List({ space: 3 }) { 1379 LazyForEach(this.data, (item: string, index: number) => { 1380 ListItem() { 1381 Row() { 1382 Text(item).fontSize(50) 1383 .onAppear(() => { 1384 console.info("appear:" + item) 1385 }) 1386 }.margin({ left: 10, right: 10 }) 1387 } 1388 .onClick(() => { 1389 // Click to delete a child component. 1390 this.data.deleteData(index); 1391 // Reset the indexes of all child components. 1392 this.data.reloadData(); 1393 }) 1394 }, (item: string, index: number) => item + index.toString()) 1395 }.cachedCount(5) 1396 } 1397 } 1398 ``` 1399 1400 After a data item is deleted, the **reloadData** method is called to rebuild the subsequent data items to update the indexes. 1401 1402 **Figure 10** Fixing unexpected data deletion 1403  1404 1405- ### Image Flickering During Re-renders 1406 1407 ```ts 1408 class BasicDataSource implements IDataSource { 1409 private listeners: DataChangeListener[] = []; 1410 private originDataArray: StringData[] = []; 1411 1412 public totalCount(): number { 1413 return 0; 1414 } 1415 1416 public getData(index: number): StringData { 1417 return this.originDataArray[index]; 1418 } 1419 1420 registerDataChangeListener(listener: DataChangeListener): void { 1421 if (this.listeners.indexOf(listener) < 0) { 1422 console.info('add listener'); 1423 this.listeners.push(listener); 1424 } 1425 } 1426 1427 unregisterDataChangeListener(listener: DataChangeListener): void { 1428 const pos = this.listeners.indexOf(listener); 1429 if (pos >= 0) { 1430 console.info('remove listener'); 1431 this.listeners.splice(pos, 1); 1432 } 1433 } 1434 1435 notifyDataReload(): void { 1436 this.listeners.forEach(listener => { 1437 listener.onDataReloaded(); 1438 }) 1439 } 1440 1441 notifyDataAdd(index: number): void { 1442 this.listeners.forEach(listener => { 1443 listener.onDataAdd(index); 1444 }) 1445 } 1446 1447 notifyDataChange(index: number): void { 1448 this.listeners.forEach(listener => { 1449 listener.onDataChange(index); 1450 }) 1451 } 1452 1453 notifyDataDelete(index: number): void { 1454 this.listeners.forEach(listener => { 1455 listener.onDataDelete(index); 1456 }) 1457 } 1458 1459 notifyDataMove(from: number, to: number): void { 1460 this.listeners.forEach(listener => { 1461 listener.onDataMove(from, to); 1462 }) 1463 } 1464 } 1465 1466 class MyDataSource extends BasicDataSource { 1467 private dataArray: StringData[] = []; 1468 1469 public totalCount(): number { 1470 return this.dataArray.length; 1471 } 1472 1473 public getData(index: number): StringData { 1474 return this.dataArray[index]; 1475 } 1476 1477 public addData(index: number, data: StringData): void { 1478 this.dataArray.splice(index, 0, data); 1479 this.notifyDataAdd(index); 1480 } 1481 1482 public pushData(data: StringData): void { 1483 this.dataArray.push(data); 1484 this.notifyDataAdd(this.dataArray.length - 1); 1485 } 1486 1487 public reloadData(): void { 1488 this.notifyDataReload(); 1489 } 1490 } 1491 1492 class StringData { 1493 message: string; 1494 imgSrc: Resource; 1495 constructor(message: string, imgSrc: Resource) { 1496 this.message = message; 1497 this.imgSrc = imgSrc; 1498 } 1499 } 1500 1501 @Entry 1502 @Component 1503 struct MyComponent { 1504 private moved: number[] = []; 1505 private data: MyDataSource = new MyDataSource(); 1506 1507 aboutToAppear() { 1508 for (let i = 0; i <= 20; i++) { 1509 this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img'))); 1510 } 1511 } 1512 1513 build() { 1514 List({ space: 3 }) { 1515 LazyForEach(this.data, (item: StringData, index: number) => { 1516 ListItem() { 1517 Column() { 1518 Text(item.message).fontSize(50) 1519 .onAppear(() => { 1520 console.info("appear:" + item.message) 1521 }) 1522 Image(item.imgSrc) 1523 .width(500) 1524 .height(200) 1525 }.margin({ left: 10, right: 10 }) 1526 } 1527 .onClick(() => { 1528 item.message += '00'; 1529 this.data.reloadData(); 1530 }) 1531 }, (item: StringData, index: number) => JSON.stringify(item)) 1532 }.cachedCount(5) 1533 } 1534 } 1535 ``` 1536 1537 **Figure 11** Unwanted image flickering with LazyForEach 1538  1539 1540 In the example, when a list item is clicked, only the **message** property of the item is changed. Yet, along with the text change comes the unwanted image flickering. This is because, with the **LazyForEach** update mechanism, the entire list item is rebuilt. As the **\<Image>** component is updated asynchronously, flickering occurs. To address this issue, use @ObjectLink and @Observed so that only the **\<Text>** component that uses the **item.message** property is re-rendered. 1541 1542 The following shows the code snippet after optimization: 1543 1544 ```ts 1545 class BasicDataSource implements IDataSource { 1546 private listeners: DataChangeListener[] = []; 1547 private originDataArray: StringData[] = []; 1548 1549 public totalCount(): number { 1550 return 0; 1551 } 1552 1553 public getData(index: number): StringData { 1554 return this.originDataArray[index]; 1555 } 1556 1557 registerDataChangeListener(listener: DataChangeListener): void { 1558 if (this.listeners.indexOf(listener) < 0) { 1559 console.info('add listener'); 1560 this.listeners.push(listener); 1561 } 1562 } 1563 1564 unregisterDataChangeListener(listener: DataChangeListener): void { 1565 const pos = this.listeners.indexOf(listener); 1566 if (pos >= 0) { 1567 console.info('remove listener'); 1568 this.listeners.splice(pos, 1); 1569 } 1570 } 1571 1572 notifyDataReload(): void { 1573 this.listeners.forEach(listener => { 1574 listener.onDataReloaded(); 1575 }) 1576 } 1577 1578 notifyDataAdd(index: number): void { 1579 this.listeners.forEach(listener => { 1580 listener.onDataAdd(index); 1581 }) 1582 } 1583 1584 notifyDataChange(index: number): void { 1585 this.listeners.forEach(listener => { 1586 listener.onDataChange(index); 1587 }) 1588 } 1589 1590 notifyDataDelete(index: number): void { 1591 this.listeners.forEach(listener => { 1592 listener.onDataDelete(index); 1593 }) 1594 } 1595 1596 notifyDataMove(from: number, to: number): void { 1597 this.listeners.forEach(listener => { 1598 listener.onDataMove(from, to); 1599 }) 1600 } 1601 } 1602 1603 class MyDataSource extends BasicDataSource { 1604 private dataArray: StringData[] = []; 1605 1606 public totalCount(): number { 1607 return this.dataArray.length; 1608 } 1609 1610 public getData(index: number): StringData { 1611 return this.dataArray[index]; 1612 } 1613 1614 public addData(index: number, data: StringData): void { 1615 this.dataArray.splice(index, 0, data); 1616 this.notifyDataAdd(index); 1617 } 1618 1619 public pushData(data: StringData): void { 1620 this.dataArray.push(data); 1621 this.notifyDataAdd(this.dataArray.length - 1); 1622 } 1623 } 1624 1625 @Observed 1626 class StringData { 1627 message: string; 1628 imgSrc: Resource; 1629 constructor(message: string, imgSrc: Resource) { 1630 this.message = message; 1631 this.imgSrc = imgSrc; 1632 } 1633 } 1634 1635 @Entry 1636 @Component 1637 struct MyComponent { 1638 @State data: MyDataSource = new MyDataSource(); 1639 1640 aboutToAppear() { 1641 for (let i = 0; i <= 20; i++) { 1642 this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img'))); 1643 } 1644 } 1645 1646 build() { 1647 List({ space: 3 }) { 1648 LazyForEach(this.data, (item: StringData, index: number) => { 1649 ListItem() { 1650 ChildComponent({data: item}) 1651 } 1652 .onClick(() => { 1653 item.message += '0'; 1654 }) 1655 }, (item: StringData, index: number) => index.toString()) 1656 }.cachedCount(5) 1657 } 1658 } 1659 1660 @Component 1661 struct ChildComponent { 1662 @ObjectLink data: StringData 1663 build() { 1664 Column() { 1665 Text(this.data.message).fontSize(50) 1666 .onAppear(() => { 1667 console.info("appear:" + this.data.message) 1668 }) 1669 Image(this.data.imgSrc) 1670 .width(500) 1671 .height(200) 1672 }.margin({ left: 10, right: 10 }) 1673 } 1674 } 1675 ``` 1676 1677 **Figure 12** Fixing unwanted image flickering 1678  1679 1680- ### UI Not Re-rendered When @ObjectLink Property Is Changed 1681 1682 ```ts 1683 class BasicDataSource implements IDataSource { 1684 private listeners: DataChangeListener[] = []; 1685 private originDataArray: StringData[] = []; 1686 1687 public totalCount(): number { 1688 return 0; 1689 } 1690 1691 public getData(index: number): StringData { 1692 return this.originDataArray[index]; 1693 } 1694 1695 registerDataChangeListener(listener: DataChangeListener): void { 1696 if (this.listeners.indexOf(listener) < 0) { 1697 console.info('add listener'); 1698 this.listeners.push(listener); 1699 } 1700 } 1701 1702 unregisterDataChangeListener(listener: DataChangeListener): void { 1703 const pos = this.listeners.indexOf(listener); 1704 if (pos >= 0) { 1705 console.info('remove listener'); 1706 this.listeners.splice(pos, 1); 1707 } 1708 } 1709 1710 notifyDataReload(): void { 1711 this.listeners.forEach(listener => { 1712 listener.onDataReloaded(); 1713 }) 1714 } 1715 1716 notifyDataAdd(index: number): void { 1717 this.listeners.forEach(listener => { 1718 listener.onDataAdd(index); 1719 }) 1720 } 1721 1722 notifyDataChange(index: number): void { 1723 this.listeners.forEach(listener => { 1724 listener.onDataChange(index); 1725 }) 1726 } 1727 1728 notifyDataDelete(index: number): void { 1729 this.listeners.forEach(listener => { 1730 listener.onDataDelete(index); 1731 }) 1732 } 1733 1734 notifyDataMove(from: number, to: number): void { 1735 this.listeners.forEach(listener => { 1736 listener.onDataMove(from, to); 1737 }) 1738 } 1739 } 1740 1741 class MyDataSource extends BasicDataSource { 1742 private dataArray: StringData[] = []; 1743 1744 public totalCount(): number { 1745 return this.dataArray.length; 1746 } 1747 1748 public getData(index: number): StringData { 1749 return this.dataArray[index]; 1750 } 1751 1752 public addData(index: number, data: StringData): void { 1753 this.dataArray.splice(index, 0, data); 1754 this.notifyDataAdd(index); 1755 } 1756 1757 public pushData(data: StringData): void { 1758 this.dataArray.push(data); 1759 this.notifyDataAdd(this.dataArray.length - 1); 1760 } 1761 } 1762 1763 @Observed 1764 class StringData { 1765 message: NestedString; 1766 constructor(message: NestedString) { 1767 this.message = message; 1768 } 1769 } 1770 1771 @Observed 1772 class NestedString { 1773 message: string; 1774 constructor(message: string) { 1775 this.message = message; 1776 } 1777 } 1778 1779 @Entry 1780 @Component 1781 struct MyComponent { 1782 private moved: number[] = []; 1783 @State data: MyDataSource = new MyDataSource(); 1784 1785 aboutToAppear() { 1786 for (let i = 0; i <= 20; i++) { 1787 this.data.pushData(new StringData(new NestedString(`Hello ${i}`))); 1788 } 1789 } 1790 1791 build() { 1792 List({ space: 3 }) { 1793 LazyForEach(this.data, (item: StringData, index: number) => { 1794 ListItem() { 1795 ChildComponent({data: item}) 1796 } 1797 .onClick(() => { 1798 item.message.message += '0'; 1799 }) 1800 }, (item: StringData, index: number) => item.toString() + index.toString()) 1801 }.cachedCount(5) 1802 } 1803 } 1804 1805 @Component 1806 struct ChildComponent { 1807 @ObjectLink data: StringData 1808 build() { 1809 Row() { 1810 Text(this.data.message.message).fontSize(50) 1811 .onAppear(() => { 1812 console.info("appear:" + this.data.message.message) 1813 }) 1814 }.margin({ left: 10, right: 10 }) 1815 } 1816 } 1817 ``` 1818 1819 **Figure 13** UI not re-rendered when @ObjectLink property is changed 1820  1821 1822 The member variable decorated by @ObjectLink can observe only changes of its sub-properties, not changes of nested properties. Therefore, to instruct a component to re-render, we need to change the component sub-properties. For details, see [\@Observed and \@ObjectLink Decorators](./arkts-observed-and-objectlink.md). 1823 1824 The following shows the code snippet after optimization: 1825 1826 ```ts 1827 class BasicDataSource implements IDataSource { 1828 private listeners: DataChangeListener[] = []; 1829 private originDataArray: StringData[] = []; 1830 1831 public totalCount(): number { 1832 return 0; 1833 } 1834 1835 public getData(index: number): StringData { 1836 return this.originDataArray[index]; 1837 } 1838 1839 registerDataChangeListener(listener: DataChangeListener): void { 1840 if (this.listeners.indexOf(listener) < 0) { 1841 console.info('add listener'); 1842 this.listeners.push(listener); 1843 } 1844 } 1845 1846 unregisterDataChangeListener(listener: DataChangeListener): void { 1847 const pos = this.listeners.indexOf(listener); 1848 if (pos >= 0) { 1849 console.info('remove listener'); 1850 this.listeners.splice(pos, 1); 1851 } 1852 } 1853 1854 notifyDataReload(): void { 1855 this.listeners.forEach(listener => { 1856 listener.onDataReloaded(); 1857 }) 1858 } 1859 1860 notifyDataAdd(index: number): void { 1861 this.listeners.forEach(listener => { 1862 listener.onDataAdd(index); 1863 }) 1864 } 1865 1866 notifyDataChange(index: number): void { 1867 this.listeners.forEach(listener => { 1868 listener.onDataChange(index); 1869 }) 1870 } 1871 1872 notifyDataDelete(index: number): void { 1873 this.listeners.forEach(listener => { 1874 listener.onDataDelete(index); 1875 }) 1876 } 1877 1878 notifyDataMove(from: number, to: number): void { 1879 this.listeners.forEach(listener => { 1880 listener.onDataMove(from, to); 1881 }) 1882 } 1883 } 1884 1885 class MyDataSource extends BasicDataSource { 1886 private dataArray: StringData[] = []; 1887 1888 public totalCount(): number { 1889 return this.dataArray.length; 1890 } 1891 1892 public getData(index: number): StringData { 1893 return this.dataArray[index]; 1894 } 1895 1896 public addData(index: number, data: StringData): void { 1897 this.dataArray.splice(index, 0, data); 1898 this.notifyDataAdd(index); 1899 } 1900 1901 public pushData(data: StringData): void { 1902 this.dataArray.push(data); 1903 this.notifyDataAdd(this.dataArray.length - 1); 1904 } 1905 } 1906 1907 @Observed 1908 class StringData { 1909 message: NestedString; 1910 constructor(message: NestedString) { 1911 this.message = message; 1912 } 1913 } 1914 1915 @Observed 1916 class NestedString { 1917 message: string; 1918 constructor(message: string) { 1919 this.message = message; 1920 } 1921 } 1922 1923 @Entry 1924 @Component 1925 struct MyComponent { 1926 private moved: number[] = []; 1927 @State data: MyDataSource = new MyDataSource(); 1928 1929 aboutToAppear() { 1930 for (let i = 0; i <= 20; i++) { 1931 this.data.pushData(new StringData(new NestedString(`Hello ${i}`))); 1932 } 1933 } 1934 1935 build() { 1936 List({ space: 3 }) { 1937 LazyForEach(this.data, (item: StringData, index: number) => { 1938 ListItem() { 1939 ChildComponent({data: item}) 1940 } 1941 .onClick(() => { 1942 item.message = new NestedString(item.message.message + '0'); 1943 }) 1944 }, (item: StringData, index: number) => item.toString() + index.toString()) 1945 }.cachedCount(5) 1946 } 1947 } 1948 1949 @Component 1950 struct ChildComponent { 1951 @ObjectLink data: StringData 1952 build() { 1953 Row() { 1954 Text(this.data.message.message).fontSize(50) 1955 .onAppear(() => { 1956 console.info("appear:" + this.data.message.message) 1957 }) 1958 }.margin({ left: 10, right: 10 }) 1959 } 1960 } 1961 ``` 1962 1963 **Figure 14** Fixing the UI-not-re-rendered issue 1964  1965