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