1# ForEach: Rendering Repeated Content 2 3**ForEach** enables array-based rendering of repeated content. 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 can be used in ArkTS widgets since API version 9. 10 11## Key Generation Rules 12 13During **ForEach** rendering, the system generates a unique and persistent key for each array element 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 a function that allows you to customize key generation rules. If no **keyGenerator** function is defined, the ArkUI framework uses the default key generation format **(item: Object, index: number) => { return index + '__' + JSON.stringify(item); }**. 16 17The ArkUI framework follows specific rules for key generation in **ForEach**, which are primarily related to the **itemGenerator** function and the second parameter **index** of the **keyGenerator** function. The specific key generation rule judgment logic is shown in the following figure. 18 19**Figure 1** ForEach key generation rules 20 21 22> **NOTE** 23> 24> The ArkUI framework issues warnings for duplicate keys. If duplicate keys exist during UI re-rendering, the framework may not work properly. For details, see [Unexpected Rendering Results](#unexpected-rendering-results). 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. Component creation involves two scenarios: [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 result for the ForEach data source without duplicate keys 69 70 71In the preceding code, the return value of the **keyGenerator** function is **item**. During **ForEach** rendering, keys (**one**, **two**, and **three**) are generated in sequence for the array items, and the corresponding **ChildItem** components are created and rendered to the UI. 72 73When the keys generated for different array items are the same, the behavior of the framework is undefined. 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 78struct 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 97struct 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 result for the ForEach data source with duplicate keys 110 111 112In this example, the final key value generation rule is **item**. When **ForEach** iterates over the data source **simpleList**, it creates a component with the key **two** and records it when it encounters the **two** at index 1. When it encounters the **two** at index 2, as the key for the current item is also **two**, no new component is created. 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, and the component corresponding to the key is re-rendered. For example, in the following code snippet, changing the value of the array's third item to **"new three"** through clicking triggers non-initial rendering by **ForEach**. 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('Click to change the value of the 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 163 164This example demonstrates that @State can observe changes to the items of a primitive data type array, such as **simpleList**. 165 1661. When any item in **simpleList** changes, **ForEach** is triggered for re-rendering. 1672. **ForEach** iterates through the new data source **['one', 'two', 'new three']** and generates the corresponding keys **one**, **two**, and **new three**. 1683. For keys **one** and **two** that exist in the previous rendering, **ForEach** reuses the corresponding components and re-renders them. For the third array item **"new three"**, since its generated key **new three** does not exist in the previous rendering, **ForEach** creates a component for it. 169 170## Use Cases 171 172**ForEach** is typically used in several cases: [static data source](#static-data-source), [changes of data source array items](#changes-of-data-source-array-items) (for example, insertions or deletions), and [property changes in data source array items](#property-changes-in-data-source-array-items). 173 174### Static Data Source 175 176If the data source remains unchanged, it can of a primitive data type. For example, when implementing loading states, you can render skeleton screens using a static list. 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 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### Changes of Data Source Array Items 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 key. 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** Effect when the data source is changed 342 343 344In this example, the **ArticleCard** component serves as a child component of the **ArticleListView** component and receives an **Article** object through the @Prop decorator to render article cards. 345 3461. When the list scrolls to the bottom with a swipe distance exceeding 80 vp, the **loadMoreArticles()** function is invoked. This function appends new elements to the **articleList** data source, increasing its length. 3472. Because the data source is decorated by @State, the ArkUI framework can detect changes in the data source length and trigger **ForEach** for re-rendering. 348 349### Property Changes in Data Source Array Items 350 351When the items in the data source decorated with @State are of a complex data type (for example, objects), the ArkUI framework cannot detect changes to the properties of the items in the data source. As a result, modifying certain properties of the items will not trigger re-rendering of **ForEach**. To achieve re-rendering of **ForEach** in such cases, use the @Observed and @ObjectLink decorators. The following example illustrates a typical use case, where clicking the like icon on an article card updates the like count of the 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** Effect when properties of data source array items are changed 455 456 457In this example, the **Article** class is decorated by the @Observed decorator. The parent component **ArticleListView** passes an **Article** instance to the child component **ArticleCard**, which receives the instance using the @ObjectLink decorator. 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 an @ObjectLink decorated state variable. Changes to its property values trigger the re-rendering of the **ArticleCard** component, which then reads the new values of **isLiked** and **likesCount**. 461 462### Drag-and-Drop Sorting 463By using **ForEach** within a List component and setting up the **onMove** event, you can implement drag-and-drop sorting. When the drag-and-drop gesture is released, if any item's position changes, the **onMove** event is triggered, which reports the original index and target index of the relocated item. In the **onMove** event, the data source must be updated based on the reported start index and target index. Before and after the data source is modified, the key 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 Column() { 473 // Clicking this button triggers re-rendering of ForEach. 474 Button('Add one item') 475 .onClick(() => { 476 this.arr.push('10'); 477 }) 478 .width(300) 479 .margin(10) 480 481 List() { 482 ForEach(this.arr, (item: string) => { 483 ListItem() { 484 Text(item.toString()) 485 .fontSize(16) 486 .textAlign(TextAlign.Center) 487 .size({ height: 100, width: "100%" }) 488 }.margin(10) 489 .borderRadius(10) 490 .backgroundColor("#FFFFFFFF") 491 }, (item: string) => item) 492 .onMove((from: number, to: number) => { 493 // The following two lines ensure that the order of components on the screen matches the order of items in the array arr. 494 // If these lines are commented out, after the first drag-and-drop sorting and subsequent addition of an item to the end of arr, the on-screen component order will align with the arr item sequence—rather than the order established by the initial drag-and-drop. This results in the loss of drag-and-drop sorting after re-rendering. 495 let tmp = this.arr.splice(from, 1); 496 this.arr.splice(to, 0, tmp[0]); 497 }) 498 } 499 .width('100%') 500 .height('100%') 501 .backgroundColor("#FFDCDCDC") 502 } 503 } 504 505 aboutToAppear(): void { 506 for (let i = 0; i < 10; i++) { 507 this.arr.push(i.toString()); 508 } 509 } 510} 511``` 512 513**Figure 8** Effect of ForEach drag-and-drop sorting 514 515 516If the two lines in the **onMove** event handler are commented out, the effect of clicking **Add one item** and triggering re-rendering is shown in the following figure. 517 518**Figure 9** ForEach drag-and-drop sorting effect not preserved after re-rendering 519 520 521## Recommendations 522 523- To ensure key uniqueness for object data, use a unique **id** property from the object data as the key. 524- Do not use the data item **index** as the key, as this can cause [unexpected rendering results](#unexpected-rendering-results) and [reduced rendering performance](#reduced-rendering-performance). If **index** must be used (for example, for conditional rendering based on **index**), be aware that data source changes will force **ForEach** to re-create components, incurring a performance cost. 525- For arrays of primitive data types, which do not have a unique ID property: If using the data item itself as the key, ensure that no duplicate values exist. For mutable data sources, convert the array to objects with unique ID properties, then use the ID as the key. 526- The **index** parameter serves as a fallback to ensure key uniqueness. When modifying a data item, since the **item** parameter in **itemGenerator** is immutable, use **index** to update the data source and trigger UI re-rendering. 527- Within [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) containers, do not use **ForEach** together with [LazyForEach](./arkts-rendering-control-lazyforeach.md). 528- When dealing with a large number of child components, **ForEach** can lead to performance issues such as lag or jank. In such cases, consider using [LazyForEach](./arkts-rendering-control-lazyforeach.md) instead. For details about the best practice, see [Performance Optimization Using LazyForEach](https://developer.huawei.com/consumer/en/doc/best-practices/bpta-lazyforeach-optimization). 529- When array items are objects, do not replace old items with new objects of the same content. If an item changes but the key remains the same, the framework may not detect the change, leading to [unrendered data updates](#data-changes-failing-to-trigger-rendering). 530## Common Pitfalls 531 532Incorrect usage of **ForEach** keys can lead to functional and performance issues, causing unexpected rendering behavior. For detailed examples, see [Unexpected Rendering Results](#unexpected-rendering-results) and [Reduced Rendering Performance](#reduced-rendering-performance). 533 534### Unexpected Rendering Results 535 536In this example, the **KeyGenerator** function, which is 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. 537 538```ts 539@Entry 540@Component 541struct Parent { 542 @State simpleList: Array<string> = ['one', 'two', 'three']; 543 544 build() { 545 Column() { 546 Button() { 547 Text('Insert Item After First Item').fontSize(30) 548 } 549 .onClick(() => { 550 this.simpleList.splice(1, 0, 'new item'); 551 }) 552 553 ForEach(this.simpleList, (item: string) => { 554 ChildItem({ item: item }) 555 }, (item: string, index: number) => index.toString()) 556 } 557 .justifyContent(FlexAlign.Center) 558 .width('100%') 559 .height('100%') 560 .backgroundColor(0xF1F3F5) 561 } 562} 563 564@Component 565struct ChildItem { 566 @Prop item: string; 567 568 build() { 569 Text(this.item) 570 .fontSize(30) 571 } 572} 573``` 574 575The following figure shows the initial screen and the screen after **Insert Item After First Item** is clicked. 576 577**Figure 10** Unexpected rendering result 578 579 580When **ForEach** is used for initial rendering, the created keys are **0**, **1**, and **2** in sequence. 581 582After 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. 583 584**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. 585 586In 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, to avoid unexpected rendering results, avoid using index-based keys for **ForEach**. 587 588### Reduced Rendering Performance 589 590In this example, the **KeyGenerator** function, which is the third parameter of **ForEach**, is omitted. According to the description in [Key Generation Rules](#key-generation-rules), the framework uses the default key format **index + '__' + JSON.stringify(item)**. Clicking the **Insert Item After First Item** button triggers component re-creation for the second array item and all subsequent items. 591 592```ts 593@Entry 594@Component 595struct Parent { 596 @State simpleList: Array<string> = ['one', 'two', 'three']; 597 598 build() { 599 Column() { 600 Button() { 601 Text('Insert Item After First Item').fontSize(30) 602 } 603 .onClick(() => { 604 this.simpleList.splice(1, 0, 'new item'); 605 console.info(`[onClick]: simpleList is [${this.simpleList.join(', ')}]`); 606 }) 607 608 ForEach(this.simpleList, (item: string) => { 609 ChildItem({ item: item }) 610 }) 611 } 612 .justifyContent(FlexAlign.Center) 613 .width('100%') 614 .height('100%') 615 .backgroundColor(0xF1F3F5) 616 } 617} 618 619@Component 620struct ChildItem { 621 @Prop item: string; 622 623 aboutToAppear() { 624 console.info(`[aboutToAppear]: item is ${this.item}`); 625 } 626 627 build() { 628 Text(this.item) 629 .fontSize(50) 630 } 631} 632``` 633 634The following figure shows the initial screen and the screen after **Insert Item After First Item** is clicked. 635 636**Figure 11** Reduced rendering performance 637 638 639After **Insert Item After First Item** is clicked, DevEco Studio displays logs as shown in the figure below. 640 641**Figure 12** Logs indicating reduced rendering performance 642 643 644After a new item is inserted, **ForEach** creates the corresponding **ChildItem** components 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. Here are the reasons: 645 6461. During initial rendering, **ForEach** generates keys **0__one**, **1__two**, and **2__three** for the initial data source ['one', 'two', 'three']. 6472. 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. 6483. **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. 649 650Although the UI rendering results in this example meet expectations, **ForEach** will re-create components for the inserted item and all subsequent items every time a new item is inserted into the middle of the array. When the data source is large or components are complex, this inability to reuse components leads to performance degradation. Therefore, avoid omitting the third parameter (**KeyGenerator**) and do not use the data index (**index**) as the key. 651 652The correct way to use **ForEach** for optimal rendering and efficiency is as follows: 653```ts 654ForEach(this.simpleList, (item: string) => { 655 ChildItem({ item: item }) 656}, (item: string) => item) // Ensure that the key is unique. 657``` 658With the use of **KeyGenerator** function, 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. 659 660### Data Changes Failing to Trigger Rendering 661When the **Like/Unlike first article** button is clicked, the first component toggles the like gesture and updates the like count. However, if the **Replace first article** button is clicked first, the **Like/Unlike first article** button does not take effect. The reason is that replacing **articleList[0]** changes the state variable **articleList**, triggering re-rendering of **ForEach**. However, since the key for the new articleList[0] remains unchanged, **ForEach** does not update the data to the child component. As a result, the first component remains bound to the old **articleList[0]**. When the property of the new **articleList[0]** is changed, the first component cannot detect the change and does not trigger re-rendering. Clicking 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. 662 663```ts 664@Observed 665class Article { 666 id: string; 667 title: string; 668 brief: string; 669 isLiked: boolean; 670 likesCount: number; 671 672 constructor(id: string, title: string, brief: string, isLiked: boolean, likesCount: number) { 673 this.id = id; 674 this.title = title; 675 this.brief = brief; 676 this.isLiked = isLiked; 677 this.likesCount = likesCount; 678 } 679} 680 681@Entry 682@Component 683struct ArticleListView { 684 @State articleList: Array<Article> = [ 685 new Article('001', 'Article 0', 'Abstract', false, 100), 686 new Article('002', 'Article 1', 'Abstract', false, 100), 687 new Article('003', 'Article 2', 'Abstract', false, 100), 688 new Article('004', 'Article 4', 'Abstract', false, 100), 689 new Article('005', 'Article 5', 'Abstract', false, 100), 690 new Article('006', 'Article 6', 'Abstract', false, 100), 691 ]; 692 693 build() { 694 Column() { 695 Button('Replace first article') 696 .onClick(() => { 697 this.articleList[0] = new Article('001', 'Article 0', 'Abstract', false, 100); 698 }) 699 .width(300) 700 .margin(10) 701 702 Button('Like/Unlike first article') 703 .onClick(() => { 704 this.articleList[0].isLiked = !this.articleList[0].isLiked; 705 this.articleList[0].likesCount = 706 this.articleList[0].isLiked ? this.articleList[0].likesCount + 1 : this.articleList[0].likesCount - 1; 707 }) 708 .width(300) 709 .margin(10) 710 711 List() { 712 ForEach(this.articleList, (item: Article) => { 713 ListItem() { 714 ArticleCard({ 715 article: item 716 }) 717 .margin({ top: 20 }) 718 } 719 }, (item: Article) => item.id) 720 } 721 .padding(20) 722 .scrollBar(BarState.Off) 723 .backgroundColor(0xF1F3F5) 724 } 725 } 726} 727 728@Component 729struct ArticleCard { 730 @ObjectLink article: Article; 731 732 handleLiked() { 733 this.article.isLiked = !this.article.isLiked; 734 this.article.likesCount = this.article.isLiked ? this.article.likesCount + 1 : this.article.likesCount - 1; 735 } 736 737 build() { 738 Row() { 739 // '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. 740 Image($r('app.media.icon')) 741 .width(80) 742 .height(80) 743 .margin({ right: 20 }) 744 745 Column() { 746 Text(this.article.title) 747 .fontSize(20) 748 .margin({ bottom: 8 }) 749 Text(this.article.brief) 750 .fontSize(16) 751 .fontColor(Color.Gray) 752 .margin({ bottom: 8 }) 753 754 Row() { 755 // 'app.media.iconLiked' and 'app.media.iconUnLiked' are only examples. Replace them with the actual ones in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed. 756 Image(this.article.isLiked ? $r('app.media.iconLiked') : $r('app.media.iconUnLiked')) 757 .width(24) 758 .height(24) 759 .margin({ right: 8 }) 760 Text(this.article.likesCount.toString()) 761 .fontSize(16) 762 } 763 .onClick(() => this.handleLiked()) 764 .justifyContent(FlexAlign.Center) 765 } 766 .alignItems(HorizontalAlign.Start) 767 .width('80%') 768 .height('100%') 769 } 770 .padding(20) 771 .borderRadius(12) 772 .backgroundColor('#FFECECEC') 773 .height(120) 774 .width('100%') 775 .justifyContent(FlexAlign.SpaceBetween) 776 } 777} 778``` 779**Figure 13** Data changes failing to trigger rendering 780 781 782### Unnecessary Memory Consumption 783If no **keyGenerator** function is defined, the ArkUI framework uses the default key generation format **(item: Object, index: number) => { return index + '__' + JSON.stringify(item); }**. When **item** is a complex object, serializing it to a JSON string results in a long string that consumes more memory. 784 785```ts 786class Data { 787 longStr: string; 788 key: string; 789 790 constructor(longStr: string, key: string) { 791 this.longStr = longStr; 792 this.key = key; 793 } 794} 795 796@Entry 797@Component 798struct Parent { 799 @State simpleList: Array<Data> = []; 800 801 aboutToAppear(): void { 802 let longStr = ''; 803 for (let i = 0; i < 2000; i++) { 804 longStr += i.toString(); 805 } 806 for (let index = 0; index < 3000; index++) { 807 let data: Data = new Data(longStr, 'a' + index.toString()); 808 this.simpleList.push(data); 809 } 810 } 811 812 build() { 813 List() { 814 ForEach(this.simpleList, (item: Data) => { 815 ListItem() { 816 Text(item.key) 817 } 818 } 819 // If the keyGenerator function is not defined, the ArkUI framework uses the default key value generation function. 820 , (item: Data) => { 821 return item.key; 822 } 823 ) 824 }.height('100%') 825 .width('100%') 826 } 827} 828``` 829 830A comparison of memory usage between the default and custom **keyGenerator** implementations shows a reduction of approximately 70 MB when a custom function is used. 831 832**Figure 14** Memory usage with default key generation 833 834 835**Figure 15** Memory usage with custom key generation 836 837