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![LazyForEach-Render-DifferentKey](./figures/LazyForEach-Render-DifferentKey.gif)
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![LazyForEach-Render-SameKey](./figures/LazyForEach-Render-SameKey.gif)
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![LazyForEach-Add-Data](./figures/LazyForEach-Add-Data.gif)
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![LazyForEach-Delete-Data](./figures/LazyForEach-Delete-Data.gif)
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![LazyForEach-Exchange-Data](./figures/LazyForEach-Exchange-Data.gif)
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![LazyForEach-Change-SingleData](./figures/LazyForEach-Change-SingleData.gif)
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![LazyForEach-Reload-Data](./figures/LazyForEach-Reload-Data.gif)
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![LazyForEach-Change-MultiData](./figures/LazyForEach-Change-MultiData.gif)
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![LazyForEach-Change-MultiData2](./figures/LazyForEach-Change-MultiData2.gif)
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![LazyForEach-Change-SubProperty](./figures/LazyForEach-Change-SubProperty.gif)
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![LazyForEach-Drag-Sort](figures/ForEach-Drag-Sort.gif)
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![LazyForEach-Render-Not-Expected](./figures/LazyForEach-Render-Not-Expected.gif)
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![LazyForEach-Render-Not-Expected-Repair](./figures/LazyForEach-Render-Not-Expected-Repair.gif)
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![LazyForEach-Image-Flush](./figures/LazyForEach-Image-Flush.gif)
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![LazyForEach-Image-Flush-Repair](./figures/LazyForEach-Image-Flush-Repair.gif)
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![LazyForEach-ObjectLink-NotRenderUI](./figures/LazyForEach-ObjectLink-NotRenderUI.gif)
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![LazyForEach-ObjectLink-NotRenderUI-Repair](./figures/LazyForEach-ObjectLink-NotRenderUI-Repair.gif)
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![LazyForEach-Screen-Flicker](figures/LazyForEach-Screen-Flicker.gif)
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![LazyForEach-Screen-Flicker-Repair](figures/LazyForEach-Screen-Flicker-Repair.gif)
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