1# LazyForEach: Lazy Data Loading 2 3For details about API parameters, see [LazyForEach](https://gitee.com/openharmony/docs/blob/master/en/application-dev/reference/apis-arkui/arkui-ts/ts-rendering-control-lazyforeach.md) APIs. 4 5**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. 6 7## Constraints 8 9- **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. 10- In each iteration, only one child component must be created for **LazyForEach**. That is, the child component generation function of **LazyForEach** has only one root component. 11- The generated child components must be allowed in the parent container component of **LazyForEach**. 12- **LazyForEach** can be included in an **if/else** statement, and can also contain such a statement. 13- The ID generation function must generate a unique value for each piece of data. Rendering issues will arise with components assigned duplicate IDs. 14- **LazyForEach** must use the **DataChangeListener** object to re-render UI. If the first parameter **dataSource** is re-assigned a value, an exception occurs. When **dataSource** uses a state variable, the change of the state variable does not trigger the UI re-renders performed by **LazyForEach**. 15- 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. 16- **LazyForEach** must be used with the [@Reusable](https://developer.huawei.com/consumer/en/doc/best-practices-V5/bpta-component-reuse-V5#section5601835174020) decorator to trigger node reuse. Use @Reusable to decorate the components on the **LazyForEach** list. For details, see [Reuse Rules](https://developer.huawei.com/consumer/en/doc/best-practices-V5/bpta-component-reuse-V5#section5923195311402). 17 18## Key Generation Rules 19 20During **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. 21 22**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: Object, index: number) => { return viewId + '-' + index.toString(); }**, wherein **viewId** is generated during compiler conversion. The **viewId** values in the same **LazyForEach** component are the same. 23 24## Component Creation Rules 25 26After 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). 27 28### Initial Render 29 30- ### Generating Different Key Values 31 32When 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. 33 34```ts 35// Basic implementation of IDataSource to handle data listener 36class BasicDataSource implements IDataSource { 37 private listeners: DataChangeListener[] = []; 38 private originDataArray: string[] = []; 39 40 public totalCount(): number { 41 return 0; 42 } 43 44 public getData(index: number): string { 45 return this.originDataArray[index]; 46 } 47 48 // This method is called by the framework to add a listener to the LazyForEach data source. 49 registerDataChangeListener(listener: DataChangeListener): void { 50 if (this.listeners.indexOf(listener) < 0) { 51 console.info('add listener'); 52 this.listeners.push(listener); 53 } 54 } 55 56 // This method is called by the framework to remove the listener from the LazyForEach data source. 57 unregisterDataChangeListener(listener: DataChangeListener): void { 58 const pos = this.listeners.indexOf(listener); 59 if (pos >= 0) { 60 console.info('remove listener'); 61 this.listeners.splice(pos, 1); 62 } 63 } 64 65 // Notify LazyForEach that all child components need to be reloaded. 66 notifyDataReload(): void { 67 this.listeners.forEach(listener => { 68 listener.onDataReloaded(); 69 }) 70 } 71 72 // Notify LazyForEach that a child component needs to be added for the data item with the specified index. 73 notifyDataAdd(index: number): void { 74 this.listeners.forEach(listener => { 75 listener.onDataAdd(index); 76 }) 77 } 78 79 // Notify LazyForEach that the data item with the specified index has changed and the child component needs to be rebuilt. 80 notifyDataChange(index: number): void { 81 this.listeners.forEach(listener => { 82 listener.onDataChange(index); 83 }) 84 } 85 86 // Notify LazyForEach that the child component needs to be deleted from the data item with the specified index. 87 notifyDataDelete(index: number): void { 88 this.listeners.forEach(listener => { 89 listener.onDataDelete(index); 90 }) 91 } 92 93 // Notify LazyForEach that data needs to be swapped between the from and to positions. 94 notifyDataMove(from: number, to: number): void { 95 this.listeners.forEach(listener => { 96 listener.onDataMove(from, to); 97 }) 98 } 99} 100 101class MyDataSource extends BasicDataSource { 102 private dataArray: string[] = []; 103 104 public totalCount(): number { 105 return this.dataArray.length; 106 } 107 108 public getData(index: number): string { 109 return this.dataArray[index]; 110 } 111 112 public addData(index: number, data: string): void { 113 this.dataArray.splice(index, 0, data); 114 this.notifyDataAdd(index); 115 } 116 117 public pushData(data: string): void { 118 this.dataArray.push(data); 119 this.notifyDataAdd(this.dataArray.length - 1); 120 } 121} 122 123@Entry 124@Component 125struct MyComponent { 126 private data: MyDataSource = new MyDataSource(); 127 128 aboutToAppear() { 129 for (let i = 0; i <= 20; i++) { 130 this.data.pushData(`Hello ${i}`) 131 } 132 } 133 134 build() { 135 List({ space: 3 }) { 136 LazyForEach(this.data, (item: string) => { 137 ListItem() { 138 Row() { 139 Text(item).fontSize(50) 140 .onAppear(() => { 141 console.info("appear:" + item) 142 }) 143 }.margin({ left: 10, right: 10 }) 144 } 145 }, (item: string) => item) 146 }.cachedCount(5) 147 } 148} 149``` 150 151In the preceding code snippets, the key generation rule is the return value **item** of the **keyGenerator** function. During loop rendering, **LazyForEach** generates keys in the sequence of **Hello 0**, **Hello 1**, ..., **Hello 20** for the array item of the data source, creates the corresponding **ListItem** child components and render them on the GUI. 152 153The figure below shows the effect. 154 155**Figure 1** Initial render of LazyForEach 156 157 158- ### Incorrect Rendering When Keys Are the Same 159 160When 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. 161 162 ```ts 163class BasicDataSource implements IDataSource { 164 private listeners: DataChangeListener[] = []; 165 private originDataArray: string[] = []; 166 167 public totalCount(): number { 168 return 0; 169 } 170 171 public getData(index: number): string { 172 return this.originDataArray[index]; 173 } 174 175 registerDataChangeListener(listener: DataChangeListener): void { 176 if (this.listeners.indexOf(listener) < 0) { 177 console.info('add listener'); 178 this.listeners.push(listener); 179 } 180 } 181 182 unregisterDataChangeListener(listener: DataChangeListener): void { 183 const pos = this.listeners.indexOf(listener); 184 if (pos >= 0) { 185 console.info('remove listener'); 186 this.listeners.splice(pos, 1); 187 } 188 } 189 190 notifyDataReload(): void { 191 this.listeners.forEach(listener => { 192 listener.onDataReloaded(); 193 }) 194 } 195 196 notifyDataAdd(index: number): void { 197 this.listeners.forEach(listener => { 198 listener.onDataAdd(index); 199 }) 200 } 201 202 notifyDataChange(index: number): void { 203 this.listeners.forEach(listener => { 204 listener.onDataChange(index); 205 }) 206 } 207 208 notifyDataDelete(index: number): void { 209 this.listeners.forEach(listener => { 210 listener.onDataDelete(index); 211 }) 212 } 213 214 notifyDataMove(from: number, to: number): void { 215 this.listeners.forEach(listener => { 216 listener.onDataMove(from, to); 217 }) 218 } 219} 220 221class MyDataSource extends BasicDataSource { 222 private dataArray: string[] = []; 223 224 public totalCount(): number { 225 return this.dataArray.length; 226 } 227 228 public getData(index: number): string { 229 return this.dataArray[index]; 230 } 231 232 public addData(index: number, data: string): void { 233 this.dataArray.splice(index, 0, data); 234 this.notifyDataAdd(index); 235 } 236 237 public pushData(data: string): void { 238 this.dataArray.push(data); 239 this.notifyDataAdd(this.dataArray.length - 1); 240 } 241} 242 243@Entry 244@Component 245struct MyComponent { 246 private data: MyDataSource = new MyDataSource(); 247 248 aboutToAppear() { 249 for (let i = 0; i <= 20; i++) { 250 this.data.pushData(`Hello ${i}`) 251 } 252 } 253 254 build() { 255 List({ space: 3 }) { 256 LazyForEach(this.data, (item: string) => { 257 ListItem() { 258 Row() { 259 Text(item).fontSize(50) 260 .onAppear(() => { 261 console.info("appear:" + item) 262 }) 263 }.margin({ left: 10, right: 10 }) 264 } 265 }, (item: string) => 'same key') 266 }.cachedCount(5) 267 } 268} 269 ``` 270 271The figure below shows the effect. 272 273**Figure 2** LazyForEach rendering when keys are the same 274 275 276### Non Initial Render 277 278When 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. 279 280- ### Adding Data 281 282```ts 283class BasicDataSource implements IDataSource { 284 private listeners: DataChangeListener[] = []; 285 private originDataArray: string[] = []; 286 287 public totalCount(): number { 288 return 0; 289 } 290 291 public getData(index: number): string { 292 return this.originDataArray[index]; 293 } 294 295 registerDataChangeListener(listener: DataChangeListener): void { 296 if (this.listeners.indexOf(listener) < 0) { 297 console.info('add listener'); 298 this.listeners.push(listener); 299 } 300 } 301 302 unregisterDataChangeListener(listener: DataChangeListener): void { 303 const pos = this.listeners.indexOf(listener); 304 if (pos >= 0) { 305 console.info('remove listener'); 306 this.listeners.splice(pos, 1); 307 } 308 } 309 310 notifyDataReload(): void { 311 this.listeners.forEach(listener => { 312 listener.onDataReloaded(); 313 }) 314 } 315 316 notifyDataAdd(index: number): void { 317 this.listeners.forEach(listener => { 318 listener.onDataAdd(index); 319 // Method 2: listener.onDatasetChange([{type: DataOperationType.ADD, index: index}]); 320 }) 321 } 322 323 notifyDataChange(index: number): void { 324 this.listeners.forEach(listener => { 325 listener.onDataChange(index); 326 }) 327 } 328 329 notifyDataDelete(index: number): void { 330 this.listeners.forEach(listener => { 331 listener.onDataDelete(index); 332 }) 333 } 334 335 notifyDataMove(from: number, to: number): void { 336 this.listeners.forEach(listener => { 337 listener.onDataMove(from, to); 338 }) 339 } 340} 341 342class MyDataSource extends BasicDataSource { 343 private dataArray: string[] = []; 344 345 public totalCount(): number { 346 return this.dataArray.length; 347 } 348 349 public getData(index: number): string { 350 return this.dataArray[index]; 351 } 352 353 public addData(index: number, data: string): void { 354 this.dataArray.splice(index, 0, data); 355 this.notifyDataAdd(index); 356 } 357 358 public pushData(data: string): void { 359 this.dataArray.push(data); 360 this.notifyDataAdd(this.dataArray.length - 1); 361 } 362} 363 364@Entry 365@Component 366struct MyComponent { 367 private data: MyDataSource = new MyDataSource(); 368 369 aboutToAppear() { 370 for (let i = 0; i <= 20; i++) { 371 this.data.pushData(`Hello ${i}`) 372 } 373 } 374 375 build() { 376 List({ space: 3 }) { 377 LazyForEach(this.data, (item: string) => { 378 ListItem() { 379 Row() { 380 Text(item).fontSize(50) 381 .onAppear(() => { 382 console.info("appear:" + item) 383 }) 384 }.margin({ left: 10, right: 10 }) 385 } 386 .onClick(() => { 387 // Click to add a child component. 388 this.data.pushData(`Hello ${this.data.totalCount()}`); 389 }) 390 }, (item: string) => item) 391 }.cachedCount(5) 392 } 393} 394``` 395 396When 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. 397 398The figure below shows the effect. 399 400**Figure 3** Adding data to LazyForEach 401 402 403- ### Deleting Data 404 405```ts 406class BasicDataSource implements IDataSource { 407 private listeners: DataChangeListener[] = []; 408 private originDataArray: string[] = []; 409 410 public totalCount(): number { 411 return 0; 412 } 413 414 public getData(index: number): string { 415 return this.originDataArray[index]; 416 } 417 418 registerDataChangeListener(listener: DataChangeListener): void { 419 if (this.listeners.indexOf(listener) < 0) { 420 console.info('add listener'); 421 this.listeners.push(listener); 422 } 423 } 424 425 unregisterDataChangeListener(listener: DataChangeListener): void { 426 const pos = this.listeners.indexOf(listener); 427 if (pos >= 0) { 428 console.info('remove listener'); 429 this.listeners.splice(pos, 1); 430 } 431 } 432 433 notifyDataReload(): void { 434 this.listeners.forEach(listener => { 435 listener.onDataReloaded(); 436 }) 437 } 438 439 notifyDataAdd(index: number): void { 440 this.listeners.forEach(listener => { 441 listener.onDataAdd(index); 442 }) 443 } 444 445 notifyDataChange(index: number): void { 446 this.listeners.forEach(listener => { 447 listener.onDataChange(index); 448 }) 449 } 450 451 notifyDataDelete(index: number): void { 452 this.listeners.forEach(listener => { 453 listener.onDataDelete(index); 454 // Method 2: listener.onDatasetChange([{type: DataOperationType.DELETE, index: index}]); 455 }) 456 } 457 458 notifyDataMove(from: number, to: number): void { 459 this.listeners.forEach(listener => { 460 listener.onDataMove(from, to); 461 }) 462 } 463} 464 465class MyDataSource extends BasicDataSource { 466 dataArray: string[] = []; 467 468 public totalCount(): number { 469 return this.dataArray.length; 470 } 471 472 public getData(index: number): string { 473 return this.dataArray[index]; 474 } 475 476 public addData(index: number, data: string): void { 477 this.dataArray.splice(index, 0, data); 478 this.notifyDataAdd(index); 479 } 480 481 public pushData(data: string): void { 482 this.dataArray.push(data); 483 } 484 485 public deleteData(index: number): void { 486 this.dataArray.splice(index, 1); 487 this.notifyDataDelete(index); 488 } 489} 490 491@Entry 492@Component 493struct MyComponent { 494 private data: MyDataSource = new MyDataSource(); 495 496 aboutToAppear() { 497 for (let i = 0; i <= 20; i++) { 498 this.data.pushData(`Hello ${i}`) 499 } 500 } 501 502 build() { 503 List({ space: 3 }) { 504 LazyForEach(this.data, (item: string, index: number) => { 505 ListItem() { 506 Row() { 507 Text(item).fontSize(50) 508 .onAppear(() => { 509 console.info("appear:" + item) 510 }) 511 }.margin({ left: 10, right: 10 }) 512 } 513 .onClick(() => { 514 // Click to delete a child component. 515 this.data.deleteData(this.data.dataArray.indexOf(item)); 516 }) 517 }, (item: string) => item) 518 }.cachedCount(5) 519 } 520} 521``` 522 523When 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. 524 525The figure below shows the effect. 526 527**Figure 4** Deleting data from LazyForEach 528 529 530- ### Swapping Data 531 532```ts 533class BasicDataSource implements IDataSource { 534 private listeners: DataChangeListener[] = []; 535 private originDataArray: string[] = []; 536 537 public totalCount(): number { 538 return 0; 539 } 540 541 public getData(index: number): string { 542 return this.originDataArray[index]; 543 } 544 545 registerDataChangeListener(listener: DataChangeListener): void { 546 if (this.listeners.indexOf(listener) < 0) { 547 console.info('add listener'); 548 this.listeners.push(listener); 549 } 550 } 551 552 unregisterDataChangeListener(listener: DataChangeListener): void { 553 const pos = this.listeners.indexOf(listener); 554 if (pos >= 0) { 555 console.info('remove listener'); 556 this.listeners.splice(pos, 1); 557 } 558 } 559 560 notifyDataReload(): void { 561 this.listeners.forEach(listener => { 562 listener.onDataReloaded(); 563 }) 564 } 565 566 notifyDataAdd(index: number): void { 567 this.listeners.forEach(listener => { 568 listener.onDataAdd(index); 569 }) 570 } 571 572 notifyDataChange(index: number): void { 573 this.listeners.forEach(listener => { 574 listener.onDataChange(index); 575 }) 576 } 577 578 notifyDataDelete(index: number): void { 579 this.listeners.forEach(listener => { 580 listener.onDataDelete(index); 581 }) 582 } 583 584 notifyDataMove(from: number, to: number): void { 585 this.listeners.forEach(listener => { 586 listener.onDataMove(from, to); 587 // Method 2: listener.onDatasetChange () 588 // [{type: DataOperationType.EXCHANGE, index: {start: from, end: to}}]); 589 }) 590 } 591} 592 593class MyDataSource extends BasicDataSource { 594 dataArray: string[] = []; 595 596 public totalCount(): number { 597 return this.dataArray.length; 598 } 599 600 public getData(index: number): string { 601 return this.dataArray[index]; 602 } 603 604 public addData(index: number, data: string): void { 605 this.dataArray.splice(index, 0, data); 606 this.notifyDataAdd(index); 607 } 608 609 public pushData(data: string): void { 610 this.dataArray.push(data); 611 } 612 613 public deleteData(index: number): void { 614 this.dataArray.splice(index, 1); 615 this.notifyDataDelete(index); 616 } 617 618 public moveData(from: number, to: number): void { 619 let temp: string = this.dataArray[from]; 620 this.dataArray[from] = this.dataArray[to]; 621 this.dataArray[to] = temp; 622 this.notifyDataMove(from, to); 623 } 624} 625 626@Entry 627@Component 628struct MyComponent { 629 private moved: number[] = []; 630 private data: MyDataSource = new MyDataSource(); 631 632 aboutToAppear() { 633 for (let i = 0; i <= 20; i++) { 634 this.data.pushData(`Hello ${i}`) 635 } 636 } 637 638 build() { 639 List({ space: 3 }) { 640 LazyForEach(this.data, (item: string, index: number) => { 641 ListItem() { 642 Row() { 643 Text(item).fontSize(50) 644 .onAppear(() => { 645 console.info("appear:" + item) 646 }) 647 }.margin({ left: 10, right: 10 }) 648 } 649 .onClick(() => { 650 this.moved.push(this.data.dataArray.indexOf(item)); 651 if (this.moved.length === 2) { 652 // Click to exchange child components. 653 this.data.moveData(this.moved[0], this.moved[1]); 654 this.moved = []; 655 } 656 }) 657 }, (item: string) => item) 658 }.cachedCount(5) 659 } 660} 661``` 662 663When 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 **notifyDataMove** 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. 664 665The figure below shows the effect. 666 667**Figure 5** Swapping data in LazyForEach 668 669 670- ### Changing a Data Item 671 672```ts 673class BasicDataSource implements IDataSource { 674 private listeners: DataChangeListener[] = []; 675 private originDataArray: string[] = []; 676 677 public totalCount(): number { 678 return 0; 679 } 680 681 public getData(index: number): string { 682 return this.originDataArray[index]; 683 } 684 685 registerDataChangeListener(listener: DataChangeListener): void { 686 if (this.listeners.indexOf(listener) < 0) { 687 console.info('add listener'); 688 this.listeners.push(listener); 689 } 690 } 691 692 unregisterDataChangeListener(listener: DataChangeListener): void { 693 const pos = this.listeners.indexOf(listener); 694 if (pos >= 0) { 695 console.info('remove listener'); 696 this.listeners.splice(pos, 1); 697 } 698 } 699 700 notifyDataReload(): void { 701 this.listeners.forEach(listener => { 702 listener.onDataReloaded(); 703 }) 704 } 705 706 notifyDataAdd(index: number): void { 707 this.listeners.forEach(listener => { 708 listener.onDataAdd(index); 709 }) 710 } 711 712 notifyDataChange(index: number): void { 713 this.listeners.forEach(listener => { 714 listener.onDataChange(index); 715 // Method 2: listener.onDatasetChange([{type: DataOperationType.CHANGE, index: index}]); 716 }) 717 } 718 719 notifyDataDelete(index: number): void { 720 this.listeners.forEach(listener => { 721 listener.onDataDelete(index); 722 }) 723 } 724 725 notifyDataMove(from: number, to: number): void { 726 this.listeners.forEach(listener => { 727 listener.onDataMove(from, to); 728 }) 729 } 730} 731 732class MyDataSource extends BasicDataSource { 733 private dataArray: string[] = []; 734 735 public totalCount(): number { 736 return this.dataArray.length; 737 } 738 739 public getData(index: number): string { 740 return this.dataArray[index]; 741 } 742 743 public addData(index: number, data: string): void { 744 this.dataArray.splice(index, 0, data); 745 this.notifyDataAdd(index); 746 } 747 748 public pushData(data: string): void { 749 this.dataArray.push(data); 750 } 751 752 public deleteData(index: number): void { 753 this.dataArray.splice(index, 1); 754 this.notifyDataDelete(index); 755 } 756 757 public changeData(index: number, data: string): void { 758 this.dataArray.splice(index, 1, data); 759 this.notifyDataChange(index); 760 } 761} 762 763@Entry 764@Component 765struct MyComponent { 766 private moved: number[] = []; 767 private data: MyDataSource = new MyDataSource(); 768 769 aboutToAppear() { 770 for (let i = 0; i <= 20; i++) { 771 this.data.pushData(`Hello ${i}`) 772 } 773 } 774 775 776 build() { 777 List({ space: 3 }) { 778 LazyForEach(this.data, (item: string, index: number) => { 779 ListItem() { 780 Row() { 781 Text(item).fontSize(50) 782 .onAppear(() => { 783 console.info("appear:" + item) 784 }) 785 }.margin({ left: 10, right: 10 }) 786 } 787 .onClick(() => { 788 this.data.changeData(index, item + '00'); 789 }) 790 }, (item: string) => item) 791 }.cachedCount(5) 792 } 793} 794``` 795 796When 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. 797 798The figure below shows the effect. 799 800**Figure 6** Changing a data item in LazyForEach 801 802 803- ### Changing Multiple Data Items 804 805```ts 806class BasicDataSource implements IDataSource { 807 private listeners: DataChangeListener[] = []; 808 private originDataArray: string[] = []; 809 810 public totalCount(): number { 811 return 0; 812 } 813 814 public getData(index: number): string { 815 return this.originDataArray[index]; 816 } 817 818 registerDataChangeListener(listener: DataChangeListener): void { 819 if (this.listeners.indexOf(listener) < 0) { 820 console.info('add listener'); 821 this.listeners.push(listener); 822 } 823 } 824 825 unregisterDataChangeListener(listener: DataChangeListener): void { 826 const pos = this.listeners.indexOf(listener); 827 if (pos >= 0) { 828 console.info('remove listener'); 829 this.listeners.splice(pos, 1); 830 } 831 } 832 833 notifyDataReload(): void { 834 this.listeners.forEach(listener => { 835 listener.onDataReloaded(); 836 // Method 2: listener.onDatasetChange([{type: DataOperationType.RELOAD}]); 837 }) 838 } 839 840 notifyDataAdd(index: number): void { 841 this.listeners.forEach(listener => { 842 listener.onDataAdd(index); 843 }) 844 } 845 846 notifyDataChange(index: number): void { 847 this.listeners.forEach(listener => { 848 listener.onDataChange(index); 849 }) 850 } 851 852 notifyDataDelete(index: number): void { 853 this.listeners.forEach(listener => { 854 listener.onDataDelete(index); 855 }) 856 } 857 858 notifyDataMove(from: number, to: number): void { 859 this.listeners.forEach(listener => { 860 listener.onDataMove(from, to); 861 }) 862 } 863} 864 865class MyDataSource extends BasicDataSource { 866 private dataArray: string[] = []; 867 868 public totalCount(): number { 869 return this.dataArray.length; 870 } 871 872 public getData(index: number): string { 873 return this.dataArray[index]; 874 } 875 876 public addData(index: number, data: string): void { 877 this.dataArray.splice(index, 0, data); 878 this.notifyDataAdd(index); 879 } 880 881 public pushData(data: string): void { 882 this.dataArray.push(data); 883 } 884 885 public deleteData(index: number): void { 886 this.dataArray.splice(index, 1); 887 this.notifyDataDelete(index); 888 } 889 890 public changeData(index: number): void { 891 this.notifyDataChange(index); 892 } 893 894 public reloadData(): void { 895 this.notifyDataReload(); 896 } 897 898 public modifyAllData(): void { 899 this.dataArray = this.dataArray.map((item: string) => { 900 return item + '0'; 901 }) 902 } 903} 904 905@Entry 906@Component 907struct MyComponent { 908 private moved: number[] = []; 909 private data: MyDataSource = new MyDataSource(); 910 911 aboutToAppear() { 912 for (let i = 0; i <= 20; i++) { 913 this.data.pushData(`Hello ${i}`) 914 } 915 } 916 917 build() { 918 List({ space: 3 }) { 919 LazyForEach(this.data, (item: string, index: number) => { 920 ListItem() { 921 Row() { 922 Text(item).fontSize(50) 923 .onAppear(() => { 924 console.info("appear:" + item) 925 }) 926 }.margin({ left: 10, right: 10 }) 927 } 928 .onClick(() => { 929 this.data.modifyAllData(); 930 this.data.reloadData(); 931 }) 932 }, (item: string) => item) 933 }.cachedCount(5) 934 } 935} 936``` 937 938When 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. 939 940The figure below shows the effect. 941 942**Figure 7** Changing multiple data items in LazyForEach 943 944 945- ### Precisely Changing Data in Batches 946 947```ts 948class BasicDataSource implements IDataSource { 949 private listeners: DataChangeListener[] = []; 950 private originDataArray: string[] = []; 951 952 public totalCount(): number { 953 return 0; 954 } 955 956 public getData(index: number): string { 957 return this.originDataArray[index]; 958 } 959 960 registerDataChangeListener(listener: DataChangeListener): void { 961 if (this.listeners.indexOf(listener) < 0) { 962 console.info('add listener'); 963 this.listeners.push(listener); 964 } 965 } 966 967 unregisterDataChangeListener(listener: DataChangeListener): void { 968 const pos = this.listeners.indexOf(listener); 969 if (pos >= 0) { 970 console.info('remove listener'); 971 this.listeners.splice(pos, 1); 972 } 973 } 974 975 notifyDatasetChange(operations: DataOperation[]): void { 976 this.listeners.forEach(listener => { 977 listener.onDatasetChange(operations); 978 }) 979 } 980} 981 982class MyDataSource extends BasicDataSource { 983 private dataArray: string[] = []; 984 985 public totalCount(): number { 986 return this.dataArray.length; 987 } 988 989 public getData(index: number): string { 990 return this.dataArray[index]; 991 } 992 993 public operateData(): void { 994 console.info(JSON.stringify(this.dataArray)); 995 this.dataArray.splice(4, 0, this.dataArray[1]); 996 this.dataArray.splice(1, 1); 997 let temp = this.dataArray[4]; 998 this.dataArray[4] = this.dataArray[6]; 999 this.dataArray[6] = temp 1000 this.dataArray.splice(8, 0, 'Hello 1', 'Hello 2'); 1001 this.dataArray.splice(12, 2); 1002 console.info(JSON.stringify(this.dataArray)); 1003 this.notifyDatasetChange([ 1004 { type: DataOperationType.MOVE, index: { from: 1, to: 3 } }, 1005 { type: DataOperationType.EXCHANGE, index: { start: 4, end: 6 } }, 1006 { type: DataOperationType.ADD, index: 8, count: 2 }, 1007 { type: DataOperationType.DELETE, index: 10, count: 2 }]); 1008 } 1009 1010 public init(): void { 1011 this.dataArray.splice(0, 0, 'Hello a', 'Hello b', 'Hello c', 'Hello d', 'Hello e', 'Hello f', 'Hello g', 'Hello h', 1012 'Hello i', 'Hello j', 'Hello k', 'Hello l', 'Hello m', 'Hello n', 'Hello o', 'Hello p', 'Hello q', 'Hello r'); 1013 } 1014} 1015 1016@Entry 1017@Component 1018struct MyComponent { 1019 private data: MyDataSource = new MyDataSource(); 1020 1021 aboutToAppear() { 1022 this.data.init() 1023 } 1024 1025 build() { 1026 Column() { 1027 Text('Move the second item to where the fourth item is located, exchange the fifth and seventh data items, add "Hello 1" "Hello 2" to the ninth item, and delete two items starting from the eleventh item') 1028 .fontSize(10) 1029 .backgroundColor(Color.Blue) 1030 .fontColor(Color.White) 1031 .borderRadius(50) 1032 .padding(5) 1033 .onClick(() => { 1034 this.data.operateData(); 1035 }) 1036 List({ space: 3 }) { 1037 LazyForEach(this.data, (item: string, index: number) => { 1038 ListItem() { 1039 Row() { 1040 Text(item).fontSize(35) 1041 .onAppear(() => { 1042 console.info("appear:" + item) 1043 }) 1044 }.margin({ left: 10, right: 10 }) 1045 } 1046 1047 }, (item: string) => item + new Date().getTime()) 1048 }.cachedCount(5) 1049 } 1050 } 1051} 1052``` 1053 1054The **onDatasetChange** API notifies **LazyForEach** of the operations to be performed at once. In the preceding example, **LazyForEach** adds, deletes, moves, and exchanges data at the same time. 1055 1056**Figure 8** Changing multiple data items in LazyForEach 1057 1058 1059 1060In the second example, values are directly changed in the array without using **splice()**. Result of **operations** is directly obtained by comparing the original array with the new array. 1061```ts 1062class BasicDataSource implements IDataSource { 1063 private listeners: DataChangeListener[] = []; 1064 private originDataArray: string[] = []; 1065 1066 public totalCount(): number { 1067 return 0; 1068 } 1069 1070 public getData(index: number): string { 1071 return this.originDataArray[index]; 1072 } 1073 1074 registerDataChangeListener(listener: DataChangeListener): void { 1075 if (this.listeners.indexOf(listener) < 0) { 1076 console.info('add listener'); 1077 this.listeners.push(listener); 1078 } 1079 } 1080 1081 unregisterDataChangeListener(listener: DataChangeListener): void { 1082 const pos = this.listeners.indexOf(listener); 1083 if (pos >= 0) { 1084 console.info('remove listener'); 1085 this.listeners.splice(pos, 1); 1086 } 1087 } 1088 1089 notifyDatasetChange(operations: DataOperation[]): void { 1090 this.listeners.forEach(listener => { 1091 listener.onDatasetChange(operations); 1092 }) 1093 } 1094} 1095 1096class MyDataSource extends BasicDataSource { 1097 private dataArray: string[] = []; 1098 1099 public totalCount(): number { 1100 return this.dataArray.length; 1101 } 1102 1103 public getData(index: number): string { 1104 return this.dataArray[index]; 1105 } 1106 1107 public operateData(): void { 1108 this.dataArray = 1109 ['Hello x', 'Hello 1', 'Hello 2', 'Hello b', 'Hello c', 'Hello e', 'Hello d', 'Hello f', 'Hello g', 'Hello h'] 1110 this.notifyDatasetChange([ 1111 { type: DataOperationType.CHANGE, index: 0 }, 1112 { type: DataOperationType.ADD, index: 1, count: 2 }, 1113 { type: DataOperationType.EXCHANGE, index: { start: 3, end: 4 } }, 1114 ]); 1115 } 1116 1117 public init(): void { 1118 this.dataArray = ['Hello a', 'Hello b', 'Hello c', 'Hello d', 'Hello e', 'Hello f', 'Hello g', 'Hello h']; 1119 } 1120} 1121 1122@Entry 1123@Component 1124struct MyComponent { 1125 private data: MyDataSource = new MyDataSource(); 1126 1127 aboutToAppear() { 1128 this.data.init() 1129 } 1130 1131 build() { 1132 Column() { 1133 Text('Multi-Data Change') 1134 .fontSize(10) 1135 .backgroundColor(Color.Blue) 1136 .fontColor(Color.White) 1137 .borderRadius(50) 1138 .padding(5) 1139 .onClick(() => { 1140 this.data.operateData(); 1141 }) 1142 List({ space: 3 }) { 1143 LazyForEach(this.data, (item: string, index: number) => { 1144 ListItem() { 1145 Row() { 1146 Text(item).fontSize(35) 1147 .onAppear(() => { 1148 console.info("appear:" + item) 1149 }) 1150 }.margin({ left: 10, right: 10 }) 1151 } 1152 1153 }, (item: string) => item + new Date().getTime()) 1154 }.cachedCount(5) 1155 } 1156 } 1157} 1158``` 1159**Figure 9** Changing multiple data items in LazyForEach 1160 1161 1162 1163Pay attention to the following when using the **onDatasetChange** API: 1164 11651. The **onDatasetChange** API cannot be used together with other data operation APIs. 11662. The input parameter of **onDatasetChange** is operations. The **index** of each operation is sourced from the original array. Therefore, the index in operations(input parameter of **onDatasetChange**) does not correspond exactly with the index in the operations on Datasource and is not negative. 1167The first example clearly illustrates this point: 1168```ts 1169// Array before modification. 1170["Hello a","Hello b","Hello c","Hello d","Hello e","Hello f","Hello g","Hello h","Hello i","Hello j","Hello k","Hello l","Hello m","Hello n","Hello o","Hello p","Hello q","Hello r"] 1171//Array after modification. 1172["Hello a","Hello c","Hello d","Hello b","Hello g","Hello f","Hello e","Hello h","Hello 1","Hello 2","Hello i","Hello j","Hello m","Hello n","Hello o","Hello p","Hello q","Hello r"] 1173``` 1174**Hello b** is changed from item 2 to item 4. Therefore, the first **operation** is written in **{ type: DataOperationType.MOVE, index: { from: 1, to: 3 } }**. 1175**Hello e** whose index is 4 and **Hello g** whose index is 6 are exchanged in the original array. Therefore, the second **operation** is written in **{ type: DataOperationType.EXCHANGE, index: { start: 4, end: 6 } }**. 1176**Hello 1** and **Hello 2** are inserted after **Hello h** whose index is 7 in the original array. Therefore, the third **operation** is written in **{ type: DataOperationType.ADD, index: 8, count: 2 }**. 1177**Hello k** whose index is 10 and **Hello l** whose index is 11 are deleted in the original array. Therefore, the fourth **operation** is written in **{ type: DataOperationType.DELETE, index: 10, count: 2 }**. 1178 11793. When **onDatasetChange** is called, the data can be operated only once for each index. If the data is operated multiple times, **LazyForEach** enables only the first operation to take effect. 11804. In operations where you can specify keys on your own, **LazyForEach** does not call the key generator to obtain keys. As such, make sure the specified keys are correct. 11815. If the API contains the **RELOAD** operation, other operations do not take effect. 1182 1183 1184 1185- ### Changing Data Subproperties 1186 1187When **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. 1188 1189```ts 1190class BasicDataSource implements IDataSource { 1191 private listeners: DataChangeListener[] = []; 1192 private originDataArray: StringData[] = []; 1193 1194 public totalCount(): number { 1195 return 0; 1196 } 1197 1198 public getData(index: number): StringData { 1199 return this.originDataArray[index]; 1200 } 1201 1202 registerDataChangeListener(listener: DataChangeListener): void { 1203 if (this.listeners.indexOf(listener) < 0) { 1204 console.info('add listener'); 1205 this.listeners.push(listener); 1206 } 1207 } 1208 1209 unregisterDataChangeListener(listener: DataChangeListener): void { 1210 const pos = this.listeners.indexOf(listener); 1211 if (pos >= 0) { 1212 console.info('remove listener'); 1213 this.listeners.splice(pos, 1); 1214 } 1215 } 1216 1217 notifyDataReload(): void { 1218 this.listeners.forEach(listener => { 1219 listener.onDataReloaded(); 1220 }) 1221 } 1222 1223 notifyDataAdd(index: number): void { 1224 this.listeners.forEach(listener => { 1225 listener.onDataAdd(index); 1226 }) 1227 } 1228 1229 notifyDataChange(index: number): void { 1230 this.listeners.forEach(listener => { 1231 listener.onDataChange(index); 1232 }) 1233 } 1234 1235 notifyDataDelete(index: number): void { 1236 this.listeners.forEach(listener => { 1237 listener.onDataDelete(index); 1238 }) 1239 } 1240 1241 notifyDataMove(from: number, to: number): void { 1242 this.listeners.forEach(listener => { 1243 listener.onDataMove(from, to); 1244 }) 1245 } 1246} 1247 1248class MyDataSource extends BasicDataSource { 1249 private dataArray: StringData[] = []; 1250 1251 public totalCount(): number { 1252 return this.dataArray.length; 1253 } 1254 1255 public getData(index: number): StringData { 1256 return this.dataArray[index]; 1257 } 1258 1259 public addData(index: number, data: StringData): void { 1260 this.dataArray.splice(index, 0, data); 1261 this.notifyDataAdd(index); 1262 } 1263 1264 public pushData(data: StringData): void { 1265 this.dataArray.push(data); 1266 this.notifyDataAdd(this.dataArray.length - 1); 1267 } 1268} 1269 1270@Observed 1271class StringData { 1272 message: string; 1273 constructor(message: string) { 1274 this.message = message; 1275 } 1276} 1277 1278@Entry 1279@Component 1280struct MyComponent { 1281 private moved: number[] = []; 1282 @State data: MyDataSource = new MyDataSource(); 1283 1284 aboutToAppear() { 1285 for (let i = 0; i <= 20; i++) { 1286 this.data.pushData(new StringData(`Hello ${i}`)); 1287 } 1288 } 1289 1290 build() { 1291 List({ space: 3 }) { 1292 LazyForEach(this.data, (item: StringData, index: number) => { 1293 ListItem() { 1294 ChildComponent({data: item}) 1295 } 1296 .onClick(() => { 1297 item.message += '0'; 1298 }) 1299 }, (item: StringData, index: number) => index.toString()) 1300 }.cachedCount(5) 1301 } 1302} 1303 1304@Component 1305struct ChildComponent { 1306 @ObjectLink data: StringData 1307 build() { 1308 Row() { 1309 Text(this.data.message).fontSize(50) 1310 .onAppear(() => { 1311 console.info("appear:" + this.data.message) 1312 }) 1313 }.margin({ left: 10, right: 10 }) 1314 } 1315} 1316``` 1317 1318When 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. 1319 1320**Figure 10** Changing data subproperties in LazyForEach 1321 1322 1323## Enabling Drag and Sort 1324If **LazyForEach** is used in a list, and the **onMove** event is set, you can enable drag and sort for the list items. If an item changes the position after you drag and sort the data, the **onMove** event is triggered to report the original index and target index of the item. The data source needs to be modified in the **onMove** event based on the reported start index and target index. The **DataChangeListener** API does not need to be called to notify the data source change. 1325 1326```ts 1327class BasicDataSource implements IDataSource { 1328 private listeners: DataChangeListener[] = []; 1329 private originDataArray: string[] = []; 1330 1331 public totalCount(): number { 1332 return 0; 1333 } 1334 1335 public getData(index: number): string { 1336 return this.originDataArray[index]; 1337 } 1338 1339 registerDataChangeListener(listener: DataChangeListener): void { 1340 if (this.listeners.indexOf(listener) < 0) { 1341 console.info('add listener'); 1342 this.listeners.push(listener); 1343 } 1344 } 1345 1346 unregisterDataChangeListener(listener: DataChangeListener): void { 1347 const pos = this.listeners.indexOf(listener); 1348 if (pos >= 0) { 1349 console.info('remove listener'); 1350 this.listeners.splice(pos, 1); 1351 } 1352 } 1353 1354 notifyDataReload(): void { 1355 this.listeners.forEach(listener => { 1356 listener.onDataReloaded(); 1357 }) 1358 } 1359 1360 notifyDataAdd(index: number): void { 1361 this.listeners.forEach(listener => { 1362 listener.onDataAdd(index); 1363 }) 1364 } 1365 1366 notifyDataChange(index: number): void { 1367 this.listeners.forEach(listener => { 1368 listener.onDataChange(index); 1369 }) 1370 } 1371 1372 notifyDataDelete(index: number): void { 1373 this.listeners.forEach(listener => { 1374 listener.onDataDelete(index); 1375 }) 1376 } 1377 1378 notifyDataMove(from: number, to: number): void { 1379 this.listeners.forEach(listener => { 1380 listener.onDataMove(from, to); 1381 }) 1382 } 1383} 1384 1385class MyDataSource extends BasicDataSource { 1386 private dataArray: string[] = []; 1387 1388 public totalCount(): number { 1389 return this.dataArray.length; 1390 } 1391 1392 public getData(index: number): string { 1393 return this.dataArray[index]; 1394 } 1395 1396 public addData(index: number, data: string): void { 1397 this.dataArray.splice(index, 0, data); 1398 this.notifyDataAdd(index); 1399 } 1400 1401 public moveDataWithoutNotify(from: number, to: number): void { 1402 let tmp = this.dataArray.splice(from, 1); 1403 this.dataArray.splice(to, 0, tmp[0]) 1404 } 1405 1406 public pushData(data: string): void { 1407 this.dataArray.push(data); 1408 this.notifyDataAdd(this.dataArray.length - 1); 1409 } 1410 1411 public deleteData(index: number): void { 1412 this.dataArray.splice(index, 1); 1413 this.notifyDataDelete(index); 1414 } 1415} 1416 1417@Entry 1418@Component 1419struct Parent { 1420 private data: MyDataSource = new MyDataSource(); 1421 1422 build() { 1423 Row() { 1424 List() { 1425 LazyForEach(this.data, (item: string) => { 1426 ListItem() { 1427 Text(item.toString()) 1428 .fontSize(16) 1429 .textAlign(TextAlign.Center) 1430 .size({height: 100, width: "100%"}) 1431 }.margin(10) 1432 .borderRadius(10) 1433 .backgroundColor("#FFFFFFFF") 1434 }, (item: string) => item) 1435 .onMove((from:number, to:number)=>{ 1436 this.data.moveDataWithoutNotify(from, to) 1437 }) 1438 } 1439 .width('100%') 1440 .height('100%') 1441 .backgroundColor("#FFDCDCDC") 1442 } 1443 } 1444 aboutToAppear(): void { 1445 for (let i = 0; i < 100; i++) { 1446 this.data.pushData(i.toString()) 1447 } 1448 } 1449} 1450``` 1451 1452**Figure 11** Drag and sort in LazyForEach 1453 1454 1455## FAQs 1456 1457- ### Unexpected Rendering Result 1458 1459 ```ts 1460 class BasicDataSource implements IDataSource { 1461 private listeners: DataChangeListener[] = []; 1462 private originDataArray: string[] = []; 1463 1464 public totalCount(): number { 1465 return 0; 1466 } 1467 1468 public getData(index: number): string { 1469 return this.originDataArray[index]; 1470 } 1471 1472 registerDataChangeListener(listener: DataChangeListener): void { 1473 if (this.listeners.indexOf(listener) < 0) { 1474 console.info('add listener'); 1475 this.listeners.push(listener); 1476 } 1477 } 1478 1479 unregisterDataChangeListener(listener: DataChangeListener): void { 1480 const pos = this.listeners.indexOf(listener); 1481 if (pos >= 0) { 1482 console.info('remove listener'); 1483 this.listeners.splice(pos, 1); 1484 } 1485 } 1486 1487 notifyDataReload(): void { 1488 this.listeners.forEach(listener => { 1489 listener.onDataReloaded(); 1490 }) 1491 } 1492 1493 notifyDataAdd(index: number): void { 1494 this.listeners.forEach(listener => { 1495 listener.onDataAdd(index); 1496 }) 1497 } 1498 1499 notifyDataChange(index: number): void { 1500 this.listeners.forEach(listener => { 1501 listener.onDataChange(index); 1502 }) 1503 } 1504 1505 notifyDataDelete(index: number): void { 1506 this.listeners.forEach(listener => { 1507 listener.onDataDelete(index); 1508 }) 1509 } 1510 1511 notifyDataMove(from: number, to: number): void { 1512 this.listeners.forEach(listener => { 1513 listener.onDataMove(from, to); 1514 }) 1515 } 1516 } 1517 1518 class MyDataSource extends BasicDataSource { 1519 private dataArray: string[] = []; 1520 1521 public totalCount(): number { 1522 return this.dataArray.length; 1523 } 1524 1525 public getData(index: number): string { 1526 return this.dataArray[index]; 1527 } 1528 1529 public addData(index: number, data: string): void { 1530 this.dataArray.splice(index, 0, data); 1531 this.notifyDataAdd(index); 1532 } 1533 1534 public pushData(data: string): void { 1535 this.dataArray.push(data); 1536 this.notifyDataAdd(this.dataArray.length - 1); 1537 } 1538 1539 public deleteData(index: number): void { 1540 this.dataArray.splice(index, 1); 1541 this.notifyDataDelete(index); 1542 } 1543 } 1544 1545 @Entry 1546 @Component 1547 struct MyComponent { 1548 private data: MyDataSource = new MyDataSource(); 1549 1550 aboutToAppear() { 1551 for (let i = 0; i <= 20; i++) { 1552 this.data.pushData(`Hello ${i}`) 1553 } 1554 } 1555 1556 build() { 1557 List({ space: 3 }) { 1558 LazyForEach(this.data, (item: string, index: number) => { 1559 ListItem() { 1560 Row() { 1561 Text(item).fontSize(50) 1562 .onAppear(() => { 1563 console.info("appear:" + item) 1564 }) 1565 }.margin({ left: 10, right: 10 }) 1566 } 1567 .onClick(() => { 1568 // Click to delete a child component. 1569 this.data.deleteData(index); 1570 }) 1571 }, (item: string) => item) 1572 }.cachedCount(5) 1573 } 1574 } 1575 ``` 1576 1577 **Figure 12** Unexpected data deletion by LazyForEach 1578  1579 1580 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. 1581 1582 The following shows the code snippet after optimization: 1583 1584 ```ts 1585 class BasicDataSource implements IDataSource { 1586 private listeners: DataChangeListener[] = []; 1587 private originDataArray: string[] = []; 1588 1589 public totalCount(): number { 1590 return 0; 1591 } 1592 1593 public getData(index: number): string { 1594 return this.originDataArray[index]; 1595 } 1596 1597 registerDataChangeListener(listener: DataChangeListener): void { 1598 if (this.listeners.indexOf(listener) < 0) { 1599 console.info('add listener'); 1600 this.listeners.push(listener); 1601 } 1602 } 1603 1604 unregisterDataChangeListener(listener: DataChangeListener): void { 1605 const pos = this.listeners.indexOf(listener); 1606 if (pos >= 0) { 1607 console.info('remove listener'); 1608 this.listeners.splice(pos, 1); 1609 } 1610 } 1611 1612 notifyDataReload(): void { 1613 this.listeners.forEach(listener => { 1614 listener.onDataReloaded(); 1615 }) 1616 } 1617 1618 notifyDataAdd(index: number): void { 1619 this.listeners.forEach(listener => { 1620 listener.onDataAdd(index); 1621 }) 1622 } 1623 1624 notifyDataChange(index: number): void { 1625 this.listeners.forEach(listener => { 1626 listener.onDataChange(index); 1627 }) 1628 } 1629 1630 notifyDataDelete(index: number): void { 1631 this.listeners.forEach(listener => { 1632 listener.onDataDelete(index); 1633 }) 1634 } 1635 1636 notifyDataMove(from: number, to: number): void { 1637 this.listeners.forEach(listener => { 1638 listener.onDataMove(from, to); 1639 }) 1640 } 1641 } 1642 1643 class MyDataSource extends BasicDataSource { 1644 private dataArray: string[] = []; 1645 1646 public totalCount(): number { 1647 return this.dataArray.length; 1648 } 1649 1650 public getData(index: number): string { 1651 return this.dataArray[index]; 1652 } 1653 1654 public addData(index: number, data: string): void { 1655 this.dataArray.splice(index, 0, data); 1656 this.notifyDataAdd(index); 1657 } 1658 1659 public pushData(data: string): void { 1660 this.dataArray.push(data); 1661 this.notifyDataAdd(this.dataArray.length - 1); 1662 } 1663 1664 public deleteData(index: number): void { 1665 this.dataArray.splice(index, 1); 1666 this.notifyDataDelete(index); 1667 } 1668 1669 public reloadData(): void { 1670 this.notifyDataReload(); 1671 } 1672 } 1673 1674 @Entry 1675 @Component 1676 struct MyComponent { 1677 private data: MyDataSource = new MyDataSource(); 1678 1679 aboutToAppear() { 1680 for (let i = 0; i <= 20; i++) { 1681 this.data.pushData(`Hello ${i}`) 1682 } 1683 } 1684 1685 build() { 1686 List({ space: 3 }) { 1687 LazyForEach(this.data, (item: string, index: number) => { 1688 ListItem() { 1689 Row() { 1690 Text(item).fontSize(50) 1691 .onAppear(() => { 1692 console.info("appear:" + item) 1693 }) 1694 }.margin({ left: 10, right: 10 }) 1695 } 1696 .onClick(() => { 1697 // Click to delete a child component. 1698 this.data.deleteData(index); 1699 // Reset the indexes of all child components. 1700 this.data.reloadData(); 1701 }) 1702 }, (item: string, index: number) => item + index.toString()) 1703 }.cachedCount(5) 1704 } 1705 } 1706 ``` 1707 1708 After a data item is deleted, the **reloadData** method is called to rebuild the subsequent data items to update the indexes. To gurantee that **reload** method will rebuild data items, we must make sure that new keys be generated for the data items. Here `item + index.toString()` gurantee that subsequent data items of the deleted one will be rebuit. If we replace key generator by `item + Data.now().toString()`, new keys will be generated for all remaining data items, so they all will be rebuilt. This way, effect is the same, but the performance is slightly inferior. 1709 1710 **Figure 13** Fixing unexpected data deletion 1711  1712 1713- ### Image Flickering During Re-renders 1714 1715 ```ts 1716 class BasicDataSource implements IDataSource { 1717 private listeners: DataChangeListener[] = []; 1718 private originDataArray: StringData[] = []; 1719 1720 public totalCount(): number { 1721 return 0; 1722 } 1723 1724 public getData(index: number): StringData { 1725 return this.originDataArray[index]; 1726 } 1727 1728 registerDataChangeListener(listener: DataChangeListener): void { 1729 if (this.listeners.indexOf(listener) < 0) { 1730 console.info('add listener'); 1731 this.listeners.push(listener); 1732 } 1733 } 1734 1735 unregisterDataChangeListener(listener: DataChangeListener): void { 1736 const pos = this.listeners.indexOf(listener); 1737 if (pos >= 0) { 1738 console.info('remove listener'); 1739 this.listeners.splice(pos, 1); 1740 } 1741 } 1742 1743 notifyDataReload(): void { 1744 this.listeners.forEach(listener => { 1745 listener.onDataReloaded(); 1746 }) 1747 } 1748 1749 notifyDataAdd(index: number): void { 1750 this.listeners.forEach(listener => { 1751 listener.onDataAdd(index); 1752 }) 1753 } 1754 1755 notifyDataChange(index: number): void { 1756 this.listeners.forEach(listener => { 1757 listener.onDataChange(index); 1758 }) 1759 } 1760 1761 notifyDataDelete(index: number): void { 1762 this.listeners.forEach(listener => { 1763 listener.onDataDelete(index); 1764 }) 1765 } 1766 1767 notifyDataMove(from: number, to: number): void { 1768 this.listeners.forEach(listener => { 1769 listener.onDataMove(from, to); 1770 }) 1771 } 1772 } 1773 1774 class MyDataSource extends BasicDataSource { 1775 private dataArray: StringData[] = []; 1776 1777 public totalCount(): number { 1778 return this.dataArray.length; 1779 } 1780 1781 public getData(index: number): StringData { 1782 return this.dataArray[index]; 1783 } 1784 1785 public addData(index: number, data: StringData): void { 1786 this.dataArray.splice(index, 0, data); 1787 this.notifyDataAdd(index); 1788 } 1789 1790 public pushData(data: StringData): void { 1791 this.dataArray.push(data); 1792 this.notifyDataAdd(this.dataArray.length - 1); 1793 } 1794 1795 public reloadData(): void { 1796 this.notifyDataReload(); 1797 } 1798 } 1799 1800 class StringData { 1801 message: string; 1802 imgSrc: Resource; 1803 constructor(message: string, imgSrc: Resource) { 1804 this.message = message; 1805 this.imgSrc = imgSrc; 1806 } 1807 } 1808 1809 @Entry 1810 @Component 1811 struct MyComponent { 1812 private moved: number[] = []; 1813 private data: MyDataSource = new MyDataSource(); 1814 1815 aboutToAppear() { 1816 for (let i = 0; i <= 20; i++) { 1817 this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img'))); 1818 } 1819 } 1820 1821 build() { 1822 List({ space: 3 }) { 1823 LazyForEach(this.data, (item: StringData, index: number) => { 1824 ListItem() { 1825 Column() { 1826 Text(item.message).fontSize(50) 1827 .onAppear(() => { 1828 console.info("appear:" + item.message) 1829 }) 1830 Image(item.imgSrc) 1831 .width(500) 1832 .height(200) 1833 }.margin({ left: 10, right: 10 }) 1834 } 1835 .onClick(() => { 1836 item.message += '00'; 1837 this.data.reloadData(); 1838 }) 1839 }, (item: StringData, index: number) => JSON.stringify(item)) 1840 }.cachedCount(5) 1841 } 1842 } 1843 ``` 1844 1845 **Figure 14** Unwanted image flickering with LazyForEach 1846  1847 1848 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. 1849 1850 The following shows the code snippet after optimization: 1851 1852 ```ts 1853 class BasicDataSource implements IDataSource { 1854 private listeners: DataChangeListener[] = []; 1855 private originDataArray: StringData[] = []; 1856 1857 public totalCount(): number { 1858 return 0; 1859 } 1860 1861 public getData(index: number): StringData { 1862 return this.originDataArray[index]; 1863 } 1864 1865 registerDataChangeListener(listener: DataChangeListener): void { 1866 if (this.listeners.indexOf(listener) < 0) { 1867 console.info('add listener'); 1868 this.listeners.push(listener); 1869 } 1870 } 1871 1872 unregisterDataChangeListener(listener: DataChangeListener): void { 1873 const pos = this.listeners.indexOf(listener); 1874 if (pos >= 0) { 1875 console.info('remove listener'); 1876 this.listeners.splice(pos, 1); 1877 } 1878 } 1879 1880 notifyDataReload(): void { 1881 this.listeners.forEach(listener => { 1882 listener.onDataReloaded(); 1883 }) 1884 } 1885 1886 notifyDataAdd(index: number): void { 1887 this.listeners.forEach(listener => { 1888 listener.onDataAdd(index); 1889 }) 1890 } 1891 1892 notifyDataChange(index: number): void { 1893 this.listeners.forEach(listener => { 1894 listener.onDataChange(index); 1895 }) 1896 } 1897 1898 notifyDataDelete(index: number): void { 1899 this.listeners.forEach(listener => { 1900 listener.onDataDelete(index); 1901 }) 1902 } 1903 1904 notifyDataMove(from: number, to: number): void { 1905 this.listeners.forEach(listener => { 1906 listener.onDataMove(from, to); 1907 }) 1908 } 1909 } 1910 1911 class MyDataSource extends BasicDataSource { 1912 private dataArray: StringData[] = []; 1913 1914 public totalCount(): number { 1915 return this.dataArray.length; 1916 } 1917 1918 public getData(index: number): StringData { 1919 return this.dataArray[index]; 1920 } 1921 1922 public addData(index: number, data: StringData): void { 1923 this.dataArray.splice(index, 0, data); 1924 this.notifyDataAdd(index); 1925 } 1926 1927 public pushData(data: StringData): void { 1928 this.dataArray.push(data); 1929 this.notifyDataAdd(this.dataArray.length - 1); 1930 } 1931 } 1932 1933 @Observed 1934 class StringData { 1935 message: string; 1936 imgSrc: Resource; 1937 constructor(message: string, imgSrc: Resource) { 1938 this.message = message; 1939 this.imgSrc = imgSrc; 1940 } 1941 } 1942 1943 @Entry 1944 @Component 1945 struct MyComponent { 1946 @State data: MyDataSource = new MyDataSource(); 1947 1948 aboutToAppear() { 1949 for (let i = 0; i <= 20; i++) { 1950 this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img'))); 1951 } 1952 } 1953 1954 build() { 1955 List({ space: 3 }) { 1956 LazyForEach(this.data, (item: StringData, index: number) => { 1957 ListItem() { 1958 ChildComponent({data: item}) 1959 } 1960 .onClick(() => { 1961 item.message += '0'; 1962 }) 1963 }, (item: StringData, index: number) => index.toString()) 1964 }.cachedCount(5) 1965 } 1966 } 1967 1968 @Component 1969 struct ChildComponent { 1970 @ObjectLink data: StringData 1971 build() { 1972 Column() { 1973 Text(this.data.message).fontSize(50) 1974 .onAppear(() => { 1975 console.info("appear:" + this.data.message) 1976 }) 1977 Image(this.data.imgSrc) 1978 .width(500) 1979 .height(200) 1980 }.margin({ left: 10, right: 10 }) 1981 } 1982 } 1983 ``` 1984 1985 **Figure 15** Fixing unwanted image flickering 1986  1987 1988- ### UI Not Re-rendered When @ObjectLink Property Is Changed 1989 1990 ```ts 1991 class BasicDataSource implements IDataSource { 1992 private listeners: DataChangeListener[] = []; 1993 private originDataArray: StringData[] = []; 1994 1995 public totalCount(): number { 1996 return 0; 1997 } 1998 1999 public getData(index: number): StringData { 2000 return this.originDataArray[index]; 2001 } 2002 2003 registerDataChangeListener(listener: DataChangeListener): void { 2004 if (this.listeners.indexOf(listener) < 0) { 2005 console.info('add listener'); 2006 this.listeners.push(listener); 2007 } 2008 } 2009 2010 unregisterDataChangeListener(listener: DataChangeListener): void { 2011 const pos = this.listeners.indexOf(listener); 2012 if (pos >= 0) { 2013 console.info('remove listener'); 2014 this.listeners.splice(pos, 1); 2015 } 2016 } 2017 2018 notifyDataReload(): void { 2019 this.listeners.forEach(listener => { 2020 listener.onDataReloaded(); 2021 }) 2022 } 2023 2024 notifyDataAdd(index: number): void { 2025 this.listeners.forEach(listener => { 2026 listener.onDataAdd(index); 2027 }) 2028 } 2029 2030 notifyDataChange(index: number): void { 2031 this.listeners.forEach(listener => { 2032 listener.onDataChange(index); 2033 }) 2034 } 2035 2036 notifyDataDelete(index: number): void { 2037 this.listeners.forEach(listener => { 2038 listener.onDataDelete(index); 2039 }) 2040 } 2041 2042 notifyDataMove(from: number, to: number): void { 2043 this.listeners.forEach(listener => { 2044 listener.onDataMove(from, to); 2045 }) 2046 } 2047 } 2048 2049 class MyDataSource extends BasicDataSource { 2050 private dataArray: StringData[] = []; 2051 2052 public totalCount(): number { 2053 return this.dataArray.length; 2054 } 2055 2056 public getData(index: number): StringData { 2057 return this.dataArray[index]; 2058 } 2059 2060 public addData(index: number, data: StringData): void { 2061 this.dataArray.splice(index, 0, data); 2062 this.notifyDataAdd(index); 2063 } 2064 2065 public pushData(data: StringData): void { 2066 this.dataArray.push(data); 2067 this.notifyDataAdd(this.dataArray.length - 1); 2068 } 2069 } 2070 2071 @Observed 2072 class StringData { 2073 message: NestedString; 2074 constructor(message: NestedString) { 2075 this.message = message; 2076 } 2077 } 2078 2079 @Observed 2080 class NestedString { 2081 message: string; 2082 constructor(message: string) { 2083 this.message = message; 2084 } 2085 } 2086 2087 @Entry 2088 @Component 2089 struct MyComponent { 2090 private moved: number[] = []; 2091 @State data: MyDataSource = new MyDataSource(); 2092 2093 aboutToAppear() { 2094 for (let i = 0; i <= 20; i++) { 2095 this.data.pushData(new StringData(new NestedString(`Hello ${i}`))); 2096 } 2097 } 2098 2099 build() { 2100 List({ space: 3 }) { 2101 LazyForEach(this.data, (item: StringData, index: number) => { 2102 ListItem() { 2103 ChildComponent({data: item}) 2104 } 2105 .onClick(() => { 2106 item.message.message += '0'; 2107 }) 2108 }, (item: StringData, index: number) => JSON.stringify(item) + index.toString()) 2109 }.cachedCount(5) 2110 } 2111 } 2112 2113 @Component 2114 struct ChildComponent { 2115 @ObjectLink data: StringData 2116 build() { 2117 Row() { 2118 Text(this.data.message.message).fontSize(50) 2119 .onAppear(() => { 2120 console.info("appear:" + this.data.message.message) 2121 }) 2122 }.margin({ left: 10, right: 10 }) 2123 } 2124 } 2125 ``` 2126 2127 **Figure 16** UI not re-rendered when @ObjectLink property is changed 2128  2129 2130 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). 2131 2132 The following shows the code snippet after optimization: 2133 2134 ```ts 2135 class BasicDataSource implements IDataSource { 2136 private listeners: DataChangeListener[] = []; 2137 private originDataArray: StringData[] = []; 2138 2139 public totalCount(): number { 2140 return 0; 2141 } 2142 2143 public getData(index: number): StringData { 2144 return this.originDataArray[index]; 2145 } 2146 2147 registerDataChangeListener(listener: DataChangeListener): void { 2148 if (this.listeners.indexOf(listener) < 0) { 2149 console.info('add listener'); 2150 this.listeners.push(listener); 2151 } 2152 } 2153 2154 unregisterDataChangeListener(listener: DataChangeListener): void { 2155 const pos = this.listeners.indexOf(listener); 2156 if (pos >= 0) { 2157 console.info('remove listener'); 2158 this.listeners.splice(pos, 1); 2159 } 2160 } 2161 2162 notifyDataReload(): void { 2163 this.listeners.forEach(listener => { 2164 listener.onDataReloaded(); 2165 }) 2166 } 2167 2168 notifyDataAdd(index: number): void { 2169 this.listeners.forEach(listener => { 2170 listener.onDataAdd(index); 2171 }) 2172 } 2173 2174 notifyDataChange(index: number): void { 2175 this.listeners.forEach(listener => { 2176 listener.onDataChange(index); 2177 }) 2178 } 2179 2180 notifyDataDelete(index: number): void { 2181 this.listeners.forEach(listener => { 2182 listener.onDataDelete(index); 2183 }) 2184 } 2185 2186 notifyDataMove(from: number, to: number): void { 2187 this.listeners.forEach(listener => { 2188 listener.onDataMove(from, to); 2189 }) 2190 } 2191 } 2192 2193 class MyDataSource extends BasicDataSource { 2194 private dataArray: StringData[] = []; 2195 2196 public totalCount(): number { 2197 return this.dataArray.length; 2198 } 2199 2200 public getData(index: number): StringData { 2201 return this.dataArray[index]; 2202 } 2203 2204 public addData(index: number, data: StringData): void { 2205 this.dataArray.splice(index, 0, data); 2206 this.notifyDataAdd(index); 2207 } 2208 2209 public pushData(data: StringData): void { 2210 this.dataArray.push(data); 2211 this.notifyDataAdd(this.dataArray.length - 1); 2212 } 2213 } 2214 2215 @Observed 2216 class StringData { 2217 message: NestedString; 2218 constructor(message: NestedString) { 2219 this.message = message; 2220 } 2221 } 2222 2223 @Observed 2224 class NestedString { 2225 message: string; 2226 constructor(message: string) { 2227 this.message = message; 2228 } 2229 } 2230 2231 @Entry 2232 @Component 2233 struct MyComponent { 2234 private moved: number[] = []; 2235 @State data: MyDataSource = new MyDataSource(); 2236 2237 aboutToAppear() { 2238 for (let i = 0; i <= 20; i++) { 2239 this.data.pushData(new StringData(new NestedString(`Hello ${i}`))); 2240 } 2241 } 2242 2243 build() { 2244 List({ space: 3 }) { 2245 LazyForEach(this.data, (item: StringData, index: number) => { 2246 ListItem() { 2247 ChildComponent({data: item}) 2248 } 2249 .onClick(() => { 2250 item.message = new NestedString(item.message.message + '0'); 2251 }) 2252 }, (item: StringData, index: number) => JSON.stringify(item) + index.toString()) 2253 }.cachedCount(5) 2254 } 2255 } 2256 2257 @Component 2258 struct ChildComponent { 2259 @ObjectLink data: StringData 2260 build() { 2261 Row() { 2262 Text(this.data.message.message).fontSize(50) 2263 .onAppear(() => { 2264 console.info("appear:" + this.data.message.message) 2265 }) 2266 }.margin({ left: 10, right: 10 }) 2267 } 2268 } 2269 ``` 2270 2271 **Figure 17** Fixing the UI-not-re-rendered issue 2272  2273 2274- ### Screen Flickering 2275List has an **onScrollIndex** callback function. When **onDataReloaded** is called in **onScrollIndex**, there is a risk of screen flickering. 2276 2277```ts 2278class BasicDataSource implements IDataSource { 2279 private listeners: DataChangeListener[] = []; 2280 private originDataArray: string[] = []; 2281 2282 public totalCount(): number { 2283 return 0; 2284 } 2285 2286 public getData(index: number): string { 2287 return this.originDataArray[index]; 2288 } 2289 2290 registerDataChangeListener(listener: DataChangeListener): void { 2291 if (this.listeners.indexOf(listener) < 0) { 2292 console.info('add listener'); 2293 this.listeners.push(listener); 2294 } 2295 } 2296 2297 unregisterDataChangeListener(listener: DataChangeListener): void { 2298 const pos = this.listeners.indexOf(listener); 2299 if (pos >= 0) { 2300 console.info('remove listener'); 2301 this.listeners.splice(pos, 1); 2302 } 2303 } 2304 2305 notifyDataReload(): void { 2306 this.listeners.forEach(listener => { 2307 listener.onDataReloaded(); 2308 // Method 2: listener.onDatasetChange([{type: DataOperationType.RELOAD}]); 2309 }) 2310 } 2311 2312 notifyDataAdd(index: number): void { 2313 this.listeners.forEach(listener => { 2314 listener.onDataAdd(index); 2315 }) 2316 } 2317 2318 notifyDataChange(index: number): void { 2319 this.listeners.forEach(listener => { 2320 listener.onDataChange(index); 2321 }) 2322 } 2323 2324 notifyDataDelete(index: number): void { 2325 this.listeners.forEach(listener => { 2326 listener.onDataDelete(index); 2327 }) 2328 } 2329 2330 notifyDataMove(from: number, to: number): void { 2331 this.listeners.forEach(listener => { 2332 listener.onDataMove(from, to); 2333 }) 2334 } 2335 2336 notifyDatasetChange(operations: DataOperation[]):void{ 2337 this.listeners.forEach(listener => { 2338 listener.onDatasetChange(operations); 2339 }) 2340 } 2341} 2342 2343class MyDataSource extends BasicDataSource { 2344 private dataArray: string[] = []; 2345 2346 public totalCount(): number { 2347 return this.dataArray.length; 2348 } 2349 2350 public getData(index: number): string { 2351 return this.dataArray[index]; 2352 } 2353 2354 public addData(index: number, data: string): void { 2355 this.dataArray.splice(index, 0, data); 2356 this.notifyDataAdd(index); 2357 } 2358 2359 public pushData(data: string): void { 2360 this.dataArray.push(data); 2361 this.notifyDataAdd(this.dataArray.length - 1); 2362 } 2363 2364 public deleteData(index: number): void { 2365 this.dataArray.splice(index, 1); 2366 this.notifyDataDelete(index); 2367 } 2368 2369 public changeData(index: number): void { 2370 this.notifyDataChange(index); 2371 } 2372 2373 operateData():void { 2374 const totalCount = this.dataArray.length; 2375 const batch=5; 2376 for (let i = totalCount; i < totalCount + batch; i++) { 2377 this.dataArray.push(`Hello ${i}`) 2378 } 2379 this.notifyDataReload(); 2380 } 2381} 2382 2383@Entry 2384@Component 2385struct MyComponent { 2386 private moved: number[] = []; 2387 private data: MyDataSource = new MyDataSource(); 2388 2389 aboutToAppear() { 2390 for (let i = 0; i <= 10; i++) { 2391 this.data.pushData(`Hello ${i}`) 2392 } 2393 } 2394 2395 build() { 2396 List({ space: 3 }) { 2397 LazyForEach(this.data, (item: string, index: number) => { 2398 ListItem() { 2399 Row() { 2400 Text(item) 2401 .width('100%') 2402 .height(80) 2403 .backgroundColor(Color.Gray) 2404 .onAppear(() => { 2405 console.info("appear:" + item) 2406 }) 2407 }.margin({ left: 10, right: 10 }) 2408 } 2409 }, (item: string) => item) 2410 }.cachedCount(10) 2411 .onScrollIndex((start, end, center) => { 2412 if (end === this.data.totalCount() - 1) { 2413 console.log('scroll to end') 2414 this.data.operateData(); 2415 } 2416 }) 2417 } 2418} 2419``` 2420 2421When **List** is scrolled to the bottom, screen flicks like the following. 2422 2423 2424Replacing **onDataReloaded** by **onDatasetChange** cannot only fix this issue but also improves load performance. 2425 2426```ts 2427class BasicDataSource implements IDataSource { 2428 private listeners: DataChangeListener[] = []; 2429 private originDataArray: string[] = []; 2430 2431 public totalCount(): number { 2432 return 0; 2433 } 2434 2435 public getData(index: number): string { 2436 return this.originDataArray[index]; 2437 } 2438 2439 registerDataChangeListener(listener: DataChangeListener): void { 2440 if (this.listeners.indexOf(listener) < 0) { 2441 console.info('add listener'); 2442 this.listeners.push(listener); 2443 } 2444 } 2445 2446 unregisterDataChangeListener(listener: DataChangeListener): void { 2447 const pos = this.listeners.indexOf(listener); 2448 if (pos >= 0) { 2449 console.info('remove listener'); 2450 this.listeners.splice(pos, 1); 2451 } 2452 } 2453 2454 notifyDataReload(): void { 2455 this.listeners.forEach(listener => { 2456 listener.onDataReloaded(); 2457 // Method 2: listener.onDatasetChange([{type: DataOperationType.RELOAD}]); 2458 }) 2459 } 2460 2461 notifyDataAdd(index: number): void { 2462 this.listeners.forEach(listener => { 2463 listener.onDataAdd(index); 2464 }) 2465 } 2466 2467 notifyDataChange(index: number): void { 2468 this.listeners.forEach(listener => { 2469 listener.onDataChange(index); 2470 }) 2471 } 2472 2473 notifyDataDelete(index: number): void { 2474 this.listeners.forEach(listener => { 2475 listener.onDataDelete(index); 2476 }) 2477 } 2478 2479 notifyDataMove(from: number, to: number): void { 2480 this.listeners.forEach(listener => { 2481 listener.onDataMove(from, to); 2482 }) 2483 } 2484 2485 notifyDatasetChange(operations: DataOperation[]):void{ 2486 this.listeners.forEach(listener => { 2487 listener.onDatasetChange(operations); 2488 }) 2489 } 2490} 2491 2492class MyDataSource extends BasicDataSource { 2493 private dataArray: string[] = []; 2494 2495 public totalCount(): number { 2496 return this.dataArray.length; 2497 } 2498 2499 public getData(index: number): string { 2500 return this.dataArray[index]; 2501 } 2502 2503 public addData(index: number, data: string): void { 2504 this.dataArray.splice(index, 0, data); 2505 this.notifyDataAdd(index); 2506 } 2507 2508 public pushData(data: string): void { 2509 this.dataArray.push(data); 2510 this.notifyDataAdd(this.dataArray.length - 1); 2511 } 2512 2513 public deleteData(index: number): void { 2514 this.dataArray.splice(index, 1); 2515 this.notifyDataDelete(index); 2516 } 2517 2518 public changeData(index: number): void { 2519 this.notifyDataChange(index); 2520 } 2521 2522 operateData():void { 2523 const totalCount = this.dataArray.length; 2524 const batch=5; 2525 for (let i = totalCount; i < totalCount + batch; i++) { 2526 this.dataArray.push(`Hello ${i}`) 2527 } 2528 this.notifyDatasetChange([{type:DataOperationType.ADD, index: totalCount-1, count:batch}]) 2529 } 2530} 2531 2532@Entry 2533@Component 2534struct MyComponent { 2535 private moved: number[] = []; 2536 private data: MyDataSource = new MyDataSource(); 2537 2538 aboutToAppear() { 2539 for (let i = 0; i <= 10; i++) { 2540 this.data.pushData(`Hello ${i}`) 2541 } 2542 } 2543 2544 build() { 2545 List({ space: 3 }) { 2546 LazyForEach(this.data, (item: string, index: number) => { 2547 ListItem() { 2548 Row() { 2549 Text(item) 2550 .width('100%') 2551 .height(80) 2552 .backgroundColor(Color.Gray) 2553 .onAppear(() => { 2554 console.info("appear:" + item) 2555 }) 2556 }.margin({ left: 10, right: 10 }) 2557 } 2558 }, (item: string) => item) 2559 }.cachedCount(10) 2560 .onScrollIndex((start, end, center) => { 2561 if (end === this.data.totalCount() - 1) { 2562 console.log('scroll to end') 2563 this.data.operateData(); 2564 } 2565 }) 2566 } 2567} 2568``` 2569 2570Fixed result 2571 2572