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