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