• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# ForEach: Rendering Repeated Content
2
3**ForEach** enables rendering of repeated content based on array type data. It must be used in a container component, and the component it returns must be one allowed inside the container component. For example, for rendering of list items, **ForEach** must be used in the [List](../reference/apis-arkui/arkui-ts/ts-container-list.md) component.
4
5For details about API parameters, see [ForEach](../reference/apis-arkui/arkui-ts/ts-rendering-control-foreach.md).
6
7> **NOTE**
8>
9> This API is supported in ArkTS widgets since API version 9.
10
11## Key Generation Rules
12
13During **ForEach** rendering, the system generates a unique, persistent key for each array item to identify the corresponding 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.
14
15**ForEach** 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 generator, that is, **(item: Object, index: number) => { return index + '__' + JSON.stringify(item); }**.
16
17The ArkUI framework has a set of specific judgment rules for **ForEach** key generation, which are mainly associated with the second parameter **index** of the **itemGenerator** function and the second parameter **index** of the **keyGenerator** function. The following figure shows the logic of the key generation rules.
18
19**Figure 1** ForEach key generation rules
20![ForEach-Key-Generation-Rules](figures/ForEach-Key-Generation-Rules.png)
21
22> **NOTE**
23>
24> The ArkUI framework warns of duplicate keys. If duplicate keys exist during UI re-rendering, the framework may not work properly. For details, see [Rendering Result Not as Expected](#rendering-result-not-as-expected).
25
26## Component Creation Rules
27
28After the key generation rules are determined, the **itemGenerator** function – the second parameter in **ForEach** – creates a component for each array item of the data source based on the rules. There are two cases for creating a component: [initial rendering](#initial-rendering) and [non-initial rendering](#non-initial-rendering).
29
30### Initial Rendering
31
32When used for initial rendering, **ForEach** generates a unique key for each array item of the data source based on the key generation rules, and creates a component.
33
34```ts
35@Entry
36@Component
37struct Parent {
38  @State simpleList: Array<string> = ['one', 'two', 'three'];
39
40  build() {
41    Row() {
42      Column() {
43        ForEach(this.simpleList, (item: string) => {
44          ChildItem({ item: item })
45        }, (item: string) => item)
46      }
47      .width('100%')
48      .height('100%')
49    }
50    .height('100%')
51    .backgroundColor(0xF1F3F5)
52  }
53}
54
55@Component
56struct ChildItem {
57  @Prop item: string;
58
59  build() {
60    Text(this.item)
61      .fontSize(50)
62  }
63}
64```
65
66The figure below shows the effect.
67
68**Figure 2** Initial rendering when the ForEach data sources do not have the same key
69![ForEach-CaseStudy-1stRender-NoDup](figures/ForEach-CaseStudy-1stRender-NoDup.png)
70
71In the preceding code snippets, the key generation rule is the return value **item** of the **keyGenerator** function. During **ForEach** rendering, keys (**one**, **two**, and **three**) are generated in sequence for data source array items, and corresponding child items are created and rendered to the UI.
72
73When the keys generated for different data items are the same, the behavior of the framework is unpredictable. For example, in the following code, when data items with the same key **two** are rendered by **ForEach**, only one **ChildItem** component, instead of multiple components with the same key, is created.
74
75 ```ts
76 @Entry
77 @Component
78 struct Parent {
79   @State simpleList: Array<string> = ['one', 'two', 'two', 'three'];
80
81   build() {
82     Row() {
83       Column() {
84         ForEach(this.simpleList, (item: string) => {
85           ChildItem({ item: item })
86         }, (item: string) => item)
87       }
88       .width('100%')
89       .height('100%')
90     }
91     .height('100%')
92     .backgroundColor(0xF1F3F5)
93   }
94 }
95
96 @Component
97 struct ChildItem {
98   @Prop item: string;
99
100   build() {
101     Text(this.item)
102       .fontSize(50)
103   }
104 }
105 ```
106
107The figure below shows the effect.
108
109**Figure 3** Initial rendering when the ForEach data sources have the same key
110![ForEach-CaseStudy-1stRender-Dup](figures/ForEach-CaseStudy-1stRender-Dup.png)
111
112In this example, the final key value generation rule is **item**. When **ForEach** traverses the data source **simpleList** and finds the key **two** whose index is **1**, **ForEach** creates a component whose key is **two** based on the final key value generation rule and marks the component. When **ForEach** finds the key **two** whose index is **2**, it does not create a component, because the key of the current item is also **two** according to the final key generation rule.
113
114### Non-Initial Rendering
115
116When **ForEach** is used for re-rendering (non-initial rendering), it checks whether the newly generated key already exists in the previous rendering. If the key does not exist, a new component is created. If the key exists, no new component is created; instead, the component corresponding to the key is re-rendered. For example, in the following code snippet, the value of the third item of the array is changed to **"new three"** through the click event, which triggers **ForEach** to perform re-rendering.
117
118```ts
119@Entry
120@Component
121struct Parent {
122  @State simpleList: Array<string> = ['one', 'two', 'three'];
123
124  build() {
125    Row() {
126      Column() {
127        Text('Change Value of Third Array Item')
128          .fontSize(24)
129          .fontColor(Color.Red)
130          .onClick(() => {
131            this.simpleList[2] = 'new three';
132          })
133
134        ForEach(this.simpleList, (item: string) => {
135          ChildItem({ item: item })
136            .margin({ top: 20 })
137        }, (item: string) => item)
138      }
139      .justifyContent(FlexAlign.Center)
140      .width('100%')
141      .height('100%')
142    }
143    .height('100%')
144    .backgroundColor(0xF1F3F5)
145  }
146}
147
148@Component
149struct ChildItem {
150  @Prop item: string;
151
152  build() {
153    Text(this.item)
154      .fontSize(30)
155  }
156}
157```
158
159The figure below shows the effect.
160
161**Figure 4** Re-rendering with ForEach
162![ForEach-Non-Initial-Render-Case-Effect](figures/ForEach-Non-Initial-Render-Case-Effect.gif)
163
164From this example, you can see that @State can observe changes in the primitive array items of the **simpleList** data source.
165
1661. When any array item in **simpleList** changes, **ForEach** is triggered for re-rendering.
1672. **ForEach** traverses the new data source **['one', 'two', 'new three']** and generates the corresponding keys **one**, **two**, and **new three**.
1683. Because keys **one** and **two** already exist in the previous rendering, **ForEach** reuses the corresponding components and re-renders them. For the third array item **"new three"**, because a new key **new three** is generated for it based on the key generation rule **item**, **ForEach** creates a component for it.
169
170## Use Cases
171
172**ForEach** is typically used in several cases: [data source unchanged](#data-source-unchanged), [data source changed](#data-source-changed) (for example, when array items are inserted or deleted), and [properties of data source array items changed](#properties-of-data-source-array-items-changed).
173
174### Data Source Unchanged
175
176If the data source remains unchanged, it can of a primitive data type. For example, when a page is loading, the skeleton screen may be used.
177
178```ts
179@Entry
180@Component
181struct ArticleList {
182  @State simpleList: Array<number> = [1, 2, 3, 4, 5];
183
184  build() {
185    Column() {
186      ForEach(this.simpleList, (item: number) => {
187        ArticleSkeletonView()
188          .margin({ top: 20 })
189      }, (item: number) => item.toString())
190    }
191    .padding(20)
192    .width('100%')
193    .height('100%')
194  }
195}
196
197@Builder
198function textArea(width: number | Resource | string = '100%', height: number | Resource | string = '100%') {
199  Row()
200    .width(width)
201    .height(height)
202    .backgroundColor('#FFF2F3F4')
203}
204
205@Component
206struct ArticleSkeletonView {
207  build() {
208    Row() {
209      Column() {
210        textArea(80, 80)
211      }
212      .margin({ right: 20 })
213
214      Column() {
215        textArea('60%', 20)
216        textArea('50%', 20)
217      }
218      .alignItems(HorizontalAlign.Start)
219      .justifyContent(FlexAlign.SpaceAround)
220      .height('100%')
221    }
222    .padding(20)
223    .borderRadius(12)
224    .backgroundColor('#FFECECEC')
225    .height(120)
226    .width('100%')
227    .justifyContent(FlexAlign.SpaceBetween)
228  }
229}
230```
231
232The figure below shows the effect.
233
234**Figure 5** Skeleton screen
235![ForEach-SkeletonScreen](figures/ForEach-SkeletonScreen.png)
236
237In this example, the data item is used as the key generation rule. Because the array items of the data source **simpleList** are different, the uniqueness of the keys can be ensured.
238
239### Data Source Changed
240
241If data source array item changes, for example, when an array item is inserted or deleted, or has its index changed, the data source should be of the object array type, and a unique ID of the object is used as the final key. For example, after a pull-to-refresh gesture is performed, newly obtained data items are added to the tail of the data source array, resulting in an increase in the length of the data source array.
242
243```ts
244class Article {
245  id: string;
246  title: string;
247  brief: string;
248
249  constructor(id: string, title: string, brief: string) {
250    this.id = id;
251    this.title = title;
252    this.brief = brief;
253  }
254}
255
256@Entry
257@Component
258struct ArticleListView {
259  @State isListReachEnd: boolean = false;
260  @State articleList: Array<Article> = [
261    new Article('001','Article 1','Abstract'),
262    new Article('002','Article 2','Abstract'),
263    new Article('003','Article 3','Abstract'),
264    new Article('004','Article 4','Abstract'),
265    new Article('005','Article 5','Abstract'),
266    new Article ('006','Article 6','Abstract')
267  ]
268
269  loadMoreArticles() {
270    this.articleList.push(new Article('007','New article','Abstract');
271  }
272
273  build() {
274    Column({ space: 5 }) {
275      List() {
276        ForEach(this.articleList, (item: Article) => {
277          ListItem() {
278            ArticleCard({ article: item })
279              .margin({ top: 20 })
280          }
281        }, (item: Article) => item.id)
282      }
283      .onReachEnd(() => {
284        this.isListReachEnd = true;
285      })
286      .parallelGesture(
287        PanGesture({ direction: PanDirection.Up, distance: 80 })
288          .onActionStart(() => {
289            if (this.isListReachEnd) {
290              this.loadMoreArticles();
291              this.isListReachEnd = false;
292            }
293          })
294      )
295      .padding(20)
296      .scrollBar(BarState.Off)
297    }
298    .width('100%')
299    .height('100%')
300    .backgroundColor(0xF1F3F5)
301  }
302}
303
304@Component
305struct ArticleCard {
306  @Prop article: Article;
307
308  build() {
309    Row() {
310      // 'app.media.icon' 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.
311      Image($r('app.media.icon'))
312        .width(80)
313        .height(80)
314        .margin({ right: 20 })
315
316      Column() {
317        Text(this.article.title)
318          .fontSize(20)
319          .margin({ bottom: 8 })
320        Text(this.article.brief)
321          .fontSize(16)
322          .fontColor(Color.Gray)
323          .margin({ bottom: 8 })
324      }
325      .alignItems(HorizontalAlign.Start)
326      .width('80%')
327      .height('100%')
328    }
329    .padding(20)
330    .borderRadius(12)
331    .backgroundColor('#FFECECEC')
332    .height(120)
333    .width('100%')
334    .justifyContent(FlexAlign.SpaceBetween)
335  }
336}
337```
338
339The following figure shows the initial screen (on the left) and the screen after a pull-to-refresh gesture (on the right).
340
341**Figure 6** When the data source is changed
342![ForEach-DataSourceArrayChange](figures/ForEach-DataSourceArrayChange.png)
343
344In this example, the **ArticleCard** component functions as a child component of the **ArticleListView** component and receives an **Article** object through the @Prop decorator to render article widgets.
345
3461. When the list scrolls to the bottom, if the distance of finger movement exceeds the threshold 80, the **loadMoreArticles()** function is triggered. This function adds a new data item to the tail of the **articleList** data source, increasing the length of the data source.
3472. Because the data source is decorated by @State, the ArkUI framework can detect the change of the data source length and trigger **ForEach** for re-rendering.
348
349### Properties of Data Source Array Items Changed
350
351If the data source array items are of the Object type, property changes of these array items cannot be detected by the ArkUI framework, because the framework cannot detect property changes of array items of complex types when the array is decorated by @State. As a result, re-rendering by **ForEach** is not performed. To trigger **ForEach** to perform re-rendering, use the @Observed and @ObjectLink decorators. In the following example, clicking the Like icon on the article list changes the number of likes for an article.
352
353```ts
354@Observed
355class Article {
356  id: string;
357  title: string;
358  brief: string;
359  isLiked: boolean;
360  likesCount: number;
361
362  constructor(id: string, title: string, brief: string, isLiked: boolean, likesCount: number) {
363    this.id = id;
364    this.title = title;
365    this.brief = brief;
366    this.isLiked = isLiked;
367    this.likesCount = likesCount;
368  }
369}
370
371@Entry
372@Component
373struct ArticleListView {
374  @State articleList: Array<Article> = [
375    new Article('001','Article 0','Abstract', false, 100),
376    new Article('002','Article 1','Abstract', false, 100),
377    new Article('003','Article 2','Abstract', false, 100),
378    new Article('004','Article 4','Abstract', false, 100),
379    new Article('005','Article 5','Abstract', false, 100),
380    new Article('006','Article 6','Abstract', false, 100),
381  ];
382
383  build() {
384    List() {
385      ForEach(this.articleList, (item: Article) => {
386        ListItem() {
387          ArticleCard({
388            article: item
389          })
390            .margin({ top: 20 })
391        }
392      }, (item: Article) => item.id)
393    }
394    .padding(20)
395    .scrollBar(BarState.Off)
396    .backgroundColor(0xF1F3F5)
397  }
398}
399
400@Component
401struct ArticleCard {
402  @ObjectLink article: Article;
403
404  handleLiked() {
405    this.article.isLiked = !this.article.isLiked;
406    this.article.likesCount = this.article.isLiked ? this.article.likesCount + 1 : this.article.likesCount - 1;
407  }
408
409  build() {
410    Row() {
411      // 'app.media.icon' 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.
412      Image($r('app.media.icon'))
413        .width(80)
414        .height(80)
415        .margin({ right: 20 })
416
417      Column() {
418        Text(this.article.title)
419          .fontSize(20)
420          .margin({ bottom: 8 })
421        Text(this.article.brief)
422          .fontSize(16)
423          .fontColor(Color.Gray)
424          .margin({ bottom: 8 })
425
426        Row() {
427          // 'app.media.iconLiked' and 'app.media.iconUnLiked' are 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.
428          Image(this.article.isLiked ? $r('app.media.iconLiked') : $r('app.media.iconUnLiked'))
429            .width(24)
430            .height(24)
431            .margin({ right: 8 })
432          Text(this.article.likesCount.toString())
433            .fontSize(16)
434        }
435        .onClick(() => this.handleLiked())
436        .justifyContent(FlexAlign.Center)
437      }
438      .alignItems(HorizontalAlign.Start)
439      .width('80%')
440      .height('100%')
441    }
442    .padding(20)
443    .borderRadius(12)
444    .backgroundColor('#FFECECEC')
445    .height(120)
446    .width('100%')
447    .justifyContent(FlexAlign.SpaceBetween)
448  }
449}
450```
451
452The following figure shows the initial screen (on the left) and the screen after the Like icon of Article 1 is clicked (on the right).
453
454**Figure 7** When properties of data source array items are changed
455![ForEach-DataSourceArraySubpropertyChange](figures/ForEach-DataSourceArraySubpropertyChange.png)
456
457In this example, the **Article** class is decorated by the @Observed decorator. The parent component **ArticleListView** passes an **Article** object instance to the child component **ArticleCard**, and the child component uses the @ObjectLink decorator to receive the instance.
458
4591. When the Like icon of Article 1 is clicked, the **handleLiked** function of the **ArticleCard** component is triggered. This function changes the values of the **isLiked** and **likesCount** properties of the **article** instance in the component pertaining to Article 1.
4602. The **article** instance is a state variable decorated by @ObjectLink. When its property value changes, the corresponding **ArticleCard** component is rendered. The changed values of **isLiked** and **likesCount** are read.
461
462### Enabling Drag and Sort
463If **ForEach** 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. Before and after the data source is modified, the value of each item must remain unchanged to ensure that the drop animation can be executed properly.
464
465```ts
466@Entry
467@Component
468struct ForEachSort {
469  @State arr: Array<string> = [];
470
471  build() {
472    Row() {
473      List() {
474        ForEach(this.arr, (item: string) => {
475          ListItem() {
476            Text(item.toString())
477              .fontSize(16)
478              .textAlign(TextAlign.Center)
479              .size({height: 100, width: "100%"})
480          }.margin(10)
481          .borderRadius(10)
482          .backgroundColor("#FFFFFFFF")
483        }, (item: string) => item)
484          .onMove((from:number, to:number) => {
485            let tmp = this.arr.splice(from, 1);
486            this.arr.splice(to, 0, tmp[0])
487          })
488      }
489      .width('100%')
490      .height('100%')
491      .backgroundColor("#FFDCDCDC")
492    }
493  }
494  aboutToAppear(): void {
495    for (let i = 0; i < 100; i++) {
496      this.arr.push(i.toString())
497    }
498  }
499}
500```
501
502**Figure 8** Drag and sort in ForEach
503![ForEach-Drag-Sort](figures/ForEach-Drag-Sort.gif)
504## Suggestions
505
506- To ensure unique keys for array items of the Object type, you are advised to use the unique IDs of objects as keys.
507- Avoid including the data item **index** in the final key generation rule to prevent [unexpected rendering results](#rendering-result-not-as-expected) and [deteriorated rendering performance](#deteriorated-rendering-performance). If including **index** is required, for example, when the list needs to be rendered based on the index, prepare for the performance loss resulting from component creation by **ForEach** to account for data source changes.
508- Data items of primitive data types do not have a unique ID. If you use the primitive data type itself as the key, you must ensure that the array items are not duplicate. In scenarios where the data source changes, you are advised to convert the array of a primitive data type into an array of the Object type with the **id** property, and then use the unique ID as the key.
509- For the preceding restriction rules, the **index** parameter is the final method for you to ensure the uniqueness of the keys. When modifying a data item, you need to use the index value to modify the data source because the **item** parameter in **itemGenerator** cannot be modified. In this way, the UI re-rendering is triggered.
510- Do not use **ForEach** together with [LazyForEach](./arkts-rendering-control-lazyforeach.md) in [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).
511- If an array item is of the object data type, you are not advised to replace the old array item with an array item with the same content. If the array item is changed but the key remains unchanged, [data changes fail to trigger renderings](#data-changes-fail-to-trigger-renderings).
512
513## Common Pitfalls
514
515Correct use of **ForEach** requires a clear understanding of the key generation rules. Incorrect use may cause functional issues, for example, [unexpected rendering results](#rendering-result-not-as-expected), or performance issues, for example, [deteriorated rendering performance](#deteriorated-rendering-performance).
516
517### Rendering Result Not as Expected
518
519In this example, the **KeyGenerator** function – the third parameter of **ForEach** – is set to use the string-type **index** property of the data source as the key generation rule. When **Insert Item After First Item** in the parent component **Parent** is clicked, an unexpected result is displayed.
520
521```ts
522@Entry
523@Component
524struct Parent {
525  @State simpleList: Array<string> = ['one', 'two', 'three'];
526
527  build() {
528    Column() {
529      Button() {
530        Text('Insert Item After First Item').fontSize(30)
531      }
532      .onClick(() => {
533        this.simpleList.splice(1, 0, 'new item');
534      })
535
536      ForEach(this.simpleList, (item: string) => {
537        ChildItem({ item: item })
538      }, (item: string, index: number) => index.toString())
539    }
540    .justifyContent(FlexAlign.Center)
541    .width('100%')
542    .height('100%')
543    .backgroundColor(0xF1F3F5)
544  }
545}
546
547@Component
548struct ChildItem {
549  @Prop item: string;
550
551  build() {
552    Text(this.item)
553      .fontSize(30)
554  }
555}
556```
557
558The following figure shows the initial screen and the screen after **Insert Item After First Item** is clicked.
559
560**Figure 9** Rendering result not as expected
561![ForEach-UnexpectedRenderingResult](figures/ForEach-UnexpectedRenderingResult.gif)
562
563When **ForEach** is used for initial rendering, the created keys are **0**, **1**, and **2** in sequence.
564
565After a new item is inserted, the data source **simpleList** changes to ['one','new item', 'two', 'three']. The ArkUI framework detects changes in the length of the @State decorated data source and triggers **ForEach** for re-rendering.
566
567**ForEach** traverses items in the new data source. When it reaches array item **one**, it generates key **0** for the item, and because the same key already exists, no new component is created. When **ForEach** reaches array item **new item**, it generates key **1** for the item, and because the same key already exists, no new component is created. When **ForEach** reaches array item **two**, it generates key **2** for the item, and because the same key already exists, no new component is created. When **ForEach** reaches array item **three**, it generates key **3** for the item, and because no same key exists, a new component **three** is created.
568
569In the preceding example, the final key generation rule includes **index**. While the expected rendering result is ['one','new item', 'two', 'three'], the actual rendering result is ['one', 'two', 'three', 'three']. Therefore, whenever possible, avoid including **index** in final key generation rule when using **ForEach**.
570
571### Deteriorated Rendering Performance
572
573In this example, the **KeyGenerator** function – the third parameter of **ForEach** – is left empty. According to the description in [Key Generation Rules](#key-generation-rules), the default key generation rule of the ArkUI framework is used. That is, the final key is the string **index + '__' + JSON.stringify(item)**. After **Insert Item After First Item** is clicked, **ForEach** recreates components for the second array item and all items after it.
574
575```ts
576@Entry
577@Component
578struct Parent {
579  @State simpleList: Array<string> = ['one', 'two', 'three'];
580
581  build() {
582    Column() {
583      Button() {
584        Text('Insert Item After First Item').fontSize(30)
585      }
586      .onClick(() => {
587        this.simpleList.splice(1, 0, 'new item');
588        console.log(`[onClick]: simpleList is ${JSON.stringify(this.simpleList)}`);
589      })
590
591      ForEach(this.simpleList, (item: string) => {
592        ChildItem({ item: item })
593      })
594    }
595    .justifyContent(FlexAlign.Center)
596    .width('100%')
597    .height('100%')
598    .backgroundColor(0xF1F3F5)
599  }
600}
601
602@Component
603struct ChildItem {
604  @Prop item: string;
605
606  aboutToAppear() {
607    console.log(`[aboutToAppear]: item is ${this.item}`);
608  }
609
610  build() {
611    Text(this.item)
612      .fontSize(50)
613  }
614}
615```
616
617The following figure shows the initial screen and the screen after **Insert Item After First Item** is clicked.
618
619**Figure 10** Deteriorated rendering performance
620![ForEach-RenderPerformanceDecrease](figures/ForEach-RenderPerformanceDecrease.gif)
621
622After **Insert Item After First Item** is clicked, DevEco Studio displays logs as shown in the figure below.
623
624**Figure 11** Logs indicating deteriorated rendering performance
625![ForEach-RenderPerformanceDecreaseLogs](figures/ForEach-RenderPerformanceDecreaseLogs.png)
626
627After a new item is inserted, **ForEach** creates the corresponding child items for the **new item**, **two**, and **three** array items, and executes the [aboutToAppear()](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#abouttoappear) callback. Below are the reasons:
628
6291. When **ForEach** is used for initial rendering, the created keys are **0__one**, **1__two** and **2__three** in sequence.
6302. After a new item is inserted, the data source **simpleList** changes to ['one','new item', 'two', 'three']. The ArkUI framework detects changes in the length of the @State decorated data source and triggers **ForEach** for re-rendering.
6313. **ForEach** traverses items in the new data source. When it reaches array item **one**, it generates key **0__one** for the item, and because the same key already exists, no new component is created. When **ForEach** reaches array item **new item**, it generates key **1__new item** for the item, and because no same key exists, a new component **new item** is created. When **ForEach** reaches array item **two**, it generates key **2__two** for the item, and because no same key exists, a new component **two** is created. When **ForEach** reaches array item **three**, it generates key **3__three** for the item, and because no same key exists, a new component **three** is created.
632
633Although the rendering result in this example is as expected, each time a new array item is inserted, **ForEach** recreates components for all array items following that array item. Because components are not reused, the performance experience can be poor when the data source contains a large volume of data or the component structure is complex. To sum up, whenever possible, do not leave the third parameter (the **KeyGenerator** function) of **ForEach** empty, or include **index** in the key generation rule.
634The following format of **ForEach** is used to correctly render and ensure efficiency:
635```ts
636ForEach(this.simpleList, (item: string) => {
637  ChildItem({ item: item })
638}, (item: string) => item)  // Ensure that the key is unique.
639```
640The third parameter **KeyGenerator** is provided. In the preceding example, different keys are generated for different data items of the data source, and the same key is generated for the same data item each time.
641
642### Data Changes Fail to Trigger Renderings
643When the **Like/UnLike first article** button is clicked, the like gesture is triggered and the number of likes changes in the first component. However, after the **Replace first article** button is clicked, the **Like/UnLike first article** button does not take effect. After the **articleList[0]** is replaced, the state variable **articleList** changes, triggering **ForEach** to re-render. However, the key generated by the new **articleList[0]** does not change, and **ForEach** does not synchronize the updated data to the child component. Therefore, the first component is still bound to the old **articleList[0]**. When the property of the new **articleList[0]** is changed, the first component cannot detect the change and fails to trigger a re-render. Touching the like icon can trigger rendering. This is because the property of the array item bound to the component is changed, the component detects the change and renders it again.
644```ts
645@Observed
646class Article {
647  id: string;
648  title: string;
649  brief: string;
650  isLiked: boolean;
651  likesCount: number;
652
653  constructor(id: string, title: string, brief: string, isLiked: boolean, likesCount: number) {
654    this.id = id;
655    this.title = title;
656    this.brief = brief;
657    this.isLiked = isLiked;
658    this.likesCount = likesCount;
659  }
660}
661
662@Entry
663@Component
664struct ArticleListView {
665  @State articleList: Array<Article> = [
666    new Article('001','Article 0','Abstract', false, 100),
667    new Article('002','Article 1','Abstract', false, 100),
668    new Article('003','Article 2','Abstract', false, 100),
669    new Article('004','Article 4','Abstract', false, 100),
670    new Article('005','Article 5','Abstract', false, 100),
671    new Article('006','Article 6','Abstract', false, 100),
672  ];
673
674  build() {
675    Column() {
676      Button('Replace first article')
677        .onClick(() => {
678          this.articleList[0] = new Article ('001','Article 0','Abstract', false, 100);
679        })
680        .width(300)
681        .margin(10)
682
683      Button('Like/Unlike first article')
684        .onClick(() => {
685          this.articleList[0].isLiked = !this.articleList[0].isLiked;
686          this.articleList[0].likesCount =
687            this.articleList[0].isLiked ? this.articleList[0].likesCount + 1 : this.articleList[0].likesCount - 1;
688        })
689        .width(300)
690        .margin(10)
691
692      List() {
693        ForEach(this.articleList, (item: Article) => {
694          ListItem() {
695            ArticleCard({
696              article: item
697            })
698              .margin({ top: 20 })
699          }
700        }, (item: Article) => item.id)
701      }
702      .padding(20)
703      .scrollBar(BarState.Off)
704      .backgroundColor(0xF1F3F5)
705    }
706  }
707}
708
709@Component
710struct ArticleCard {
711  @ObjectLink article: Article;
712
713  handleLiked() {
714    this.article.isLiked = !this.article.isLiked;
715    this.article.likesCount = this.article.isLiked ? this.article.likesCount + 1 : this.article.likesCount - 1;
716  }
717
718  build() {
719    Row() {
720      // 'app.media.icon' 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.
721      Image($r('app.media.icon'))
722        .width(80)
723        .height(80)
724        .margin({ right: 20 })
725
726      Column() {
727        Text(this.article.title)
728          .fontSize(20)
729          .margin({ bottom: 8 })
730        Text(this.article.brief)
731          .fontSize(16)
732          .fontColor(Color.Gray)
733          .margin({ bottom: 8 })
734
735        Row() {
736          // 'app.media.iconLiked' and 'app.media.iconUnLiked' are 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.
737          Image(this.article.isLiked ? $r('app.media.iconLiked') : $r('app.media.iconUnLiked'))
738            .width(24)
739            .height(24)
740            .margin({ right: 8 })
741          Text(this.article.likesCount.toString())
742            .fontSize(16)
743        }
744        .onClick(() => this.handleLiked())
745        .justifyContent(FlexAlign.Center)
746      }
747      .alignItems(HorizontalAlign.Start)
748      .width('80%')
749      .height('100%')
750    }
751    .padding(20)
752    .borderRadius(12)
753    .backgroundColor('#FFECECEC')
754    .height(120)
755    .width('100%')
756    .justifyContent(FlexAlign.SpaceBetween)
757  }
758}
759```
760**Figure 12** Data changes fail to trigger renderings
761![ForEach-StateVarNoRender](figures/ForEach-StateVarNoRender.PNG)
762