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