1e41f4b71Sopenharmony_ci# ForEach: Rendering Repeated Content
2e41f4b71Sopenharmony_ci
3e41f4b71Sopenharmony_ciFor details about API parameters, see [ForEach](https://gitee.com/openharmony/docs/blob/master/en/application-dev/reference/apis-arkui/arkui-ts/ts-rendering-control-foreach.md) APIs.
4e41f4b71Sopenharmony_ci
5e41f4b71Sopenharmony_ci**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](https://gitee.com/openharmony/docs/blob/master/en/application-dev/reference/apis-arkui/arkui-ts/ts-container-list.md) component.
6e41f4b71Sopenharmony_ci
7e41f4b71Sopenharmony_ci> **NOTE**
8e41f4b71Sopenharmony_ci>
9e41f4b71Sopenharmony_ci> This API is supported in ArkTS widgets since API version 9.
10e41f4b71Sopenharmony_ci
11e41f4b71Sopenharmony_ci## Key Generation Rules
12e41f4b71Sopenharmony_ci
13e41f4b71Sopenharmony_ciDuring **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 new component based on the new key.
14e41f4b71Sopenharmony_ci
15e41f4b71Sopenharmony_ci**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); }**.
16e41f4b71Sopenharmony_ci
17e41f4b71Sopenharmony_ciThe 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.
18e41f4b71Sopenharmony_ci
19e41f4b71Sopenharmony_ci**Figure 1** ForEach key generation rules 
20e41f4b71Sopenharmony_ci![ForEach-Key-Generation-Rules](figures/ForEach-Key-Generation-Rules.png)
21e41f4b71Sopenharmony_ci
22e41f4b71Sopenharmony_ci> **NOTE**
23e41f4b71Sopenharmony_ci>
24e41f4b71Sopenharmony_ci> 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).
25e41f4b71Sopenharmony_ci
26e41f4b71Sopenharmony_ci## Component Creation Rules
27e41f4b71Sopenharmony_ci
28e41f4b71Sopenharmony_ciAfter 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).
29e41f4b71Sopenharmony_ci
30e41f4b71Sopenharmony_ci### Initial Rendering
31e41f4b71Sopenharmony_ci
32e41f4b71Sopenharmony_ciWhen 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.
33e41f4b71Sopenharmony_ci
34e41f4b71Sopenharmony_ci```ts
35e41f4b71Sopenharmony_ci@Entry
36e41f4b71Sopenharmony_ci@Component
37e41f4b71Sopenharmony_cistruct Parent {
38e41f4b71Sopenharmony_ci  @State simpleList: Array<string> = ['one', 'two', 'three'];
39e41f4b71Sopenharmony_ci
40e41f4b71Sopenharmony_ci  build() {
41e41f4b71Sopenharmony_ci    Row() {
42e41f4b71Sopenharmony_ci      Column() {
43e41f4b71Sopenharmony_ci        ForEach(this.simpleList, (item: string) => {
44e41f4b71Sopenharmony_ci          ChildItem({ item: item })
45e41f4b71Sopenharmony_ci        }, (item: string) => item)
46e41f4b71Sopenharmony_ci      }
47e41f4b71Sopenharmony_ci      .width('100%')
48e41f4b71Sopenharmony_ci      .height('100%')
49e41f4b71Sopenharmony_ci    }
50e41f4b71Sopenharmony_ci    .height('100%')
51e41f4b71Sopenharmony_ci    .backgroundColor(0xF1F3F5)
52e41f4b71Sopenharmony_ci  }
53e41f4b71Sopenharmony_ci}
54e41f4b71Sopenharmony_ci
55e41f4b71Sopenharmony_ci@Component
56e41f4b71Sopenharmony_cistruct ChildItem {
57e41f4b71Sopenharmony_ci  @Prop item: string;
58e41f4b71Sopenharmony_ci
59e41f4b71Sopenharmony_ci  build() {
60e41f4b71Sopenharmony_ci    Text(this.item)
61e41f4b71Sopenharmony_ci      .fontSize(50)
62e41f4b71Sopenharmony_ci  }
63e41f4b71Sopenharmony_ci}
64e41f4b71Sopenharmony_ci```
65e41f4b71Sopenharmony_ci
66e41f4b71Sopenharmony_ciThe figure below shows the effect.
67e41f4b71Sopenharmony_ci
68e41f4b71Sopenharmony_ci**Figure 2** Initial rendering when the ForEach data sources do not have the same key 
69e41f4b71Sopenharmony_ci![ForEach-CaseStudy-1stRender-NoDup](figures/ForEach-CaseStudy-1stRender-NoDup.png)
70e41f4b71Sopenharmony_ci
71e41f4b71Sopenharmony_ciIn 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.
72e41f4b71Sopenharmony_ci
73e41f4b71Sopenharmony_ciWhen 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.
74e41f4b71Sopenharmony_ci
75e41f4b71Sopenharmony_ci ```ts
76e41f4b71Sopenharmony_ci @Entry
77e41f4b71Sopenharmony_ci @Component
78e41f4b71Sopenharmony_ci struct Parent {
79e41f4b71Sopenharmony_ci   @State simpleList: Array<string> = ['one', 'two', 'two', 'three'];
80e41f4b71Sopenharmony_ci 
81e41f4b71Sopenharmony_ci   build() {
82e41f4b71Sopenharmony_ci     Row() {
83e41f4b71Sopenharmony_ci       Column() {
84e41f4b71Sopenharmony_ci         ForEach(this.simpleList, (item: string) => {
85e41f4b71Sopenharmony_ci           ChildItem({ item: item })
86e41f4b71Sopenharmony_ci         }, (item: string) => item)
87e41f4b71Sopenharmony_ci       }
88e41f4b71Sopenharmony_ci       .width('100%')
89e41f4b71Sopenharmony_ci       .height('100%')
90e41f4b71Sopenharmony_ci     }
91e41f4b71Sopenharmony_ci     .height('100%')
92e41f4b71Sopenharmony_ci     .backgroundColor(0xF1F3F5)
93e41f4b71Sopenharmony_ci   }
94e41f4b71Sopenharmony_ci }
95e41f4b71Sopenharmony_ci 
96e41f4b71Sopenharmony_ci @Component
97e41f4b71Sopenharmony_ci struct ChildItem {
98e41f4b71Sopenharmony_ci   @Prop item: string;
99e41f4b71Sopenharmony_ci 
100e41f4b71Sopenharmony_ci   build() {
101e41f4b71Sopenharmony_ci     Text(this.item)
102e41f4b71Sopenharmony_ci       .fontSize(50)
103e41f4b71Sopenharmony_ci   }
104e41f4b71Sopenharmony_ci }
105e41f4b71Sopenharmony_ci ```
106e41f4b71Sopenharmony_ci
107e41f4b71Sopenharmony_ciThe figure below shows the effect.
108e41f4b71Sopenharmony_ci
109e41f4b71Sopenharmony_ci**Figure 3** Initial rendering when the ForEach data sources have the same key 
110e41f4b71Sopenharmony_ci![ForEach-CaseStudy-1stRender-Dup](figures/ForEach-CaseStudy-1stRender-Dup.png)
111e41f4b71Sopenharmony_ci
112e41f4b71Sopenharmony_ciIn 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.
113e41f4b71Sopenharmony_ci
114e41f4b71Sopenharmony_ci### Non-Initial Rendering
115e41f4b71Sopenharmony_ci
116e41f4b71Sopenharmony_ciWhen **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.
117e41f4b71Sopenharmony_ci
118e41f4b71Sopenharmony_ci```ts
119e41f4b71Sopenharmony_ci@Entry
120e41f4b71Sopenharmony_ci@Component
121e41f4b71Sopenharmony_cistruct Parent {
122e41f4b71Sopenharmony_ci  @State simpleList: Array<string> = ['one', 'two', 'three'];
123e41f4b71Sopenharmony_ci
124e41f4b71Sopenharmony_ci  build() {
125e41f4b71Sopenharmony_ci    Row() {
126e41f4b71Sopenharmony_ci      Column() {
127e41f4b71Sopenharmony_ci        Text('Change Value of Third Array Item')
128e41f4b71Sopenharmony_ci          .fontSize(24)
129e41f4b71Sopenharmony_ci          .fontColor(Color.Red)
130e41f4b71Sopenharmony_ci          .onClick(() => {
131e41f4b71Sopenharmony_ci            this.simpleList[2] = 'new three';
132e41f4b71Sopenharmony_ci          })
133e41f4b71Sopenharmony_ci
134e41f4b71Sopenharmony_ci        ForEach(this.simpleList, (item: string) => {
135e41f4b71Sopenharmony_ci          ChildItem({ item: item })
136e41f4b71Sopenharmony_ci            .margin({ top: 20 })
137e41f4b71Sopenharmony_ci        }, (item: string) => item)
138e41f4b71Sopenharmony_ci      }
139e41f4b71Sopenharmony_ci      .justifyContent(FlexAlign.Center)
140e41f4b71Sopenharmony_ci      .width('100%')
141e41f4b71Sopenharmony_ci      .height('100%')
142e41f4b71Sopenharmony_ci    }
143e41f4b71Sopenharmony_ci    .height('100%')
144e41f4b71Sopenharmony_ci    .backgroundColor(0xF1F3F5)
145e41f4b71Sopenharmony_ci  }
146e41f4b71Sopenharmony_ci}
147e41f4b71Sopenharmony_ci
148e41f4b71Sopenharmony_ci@Component
149e41f4b71Sopenharmony_cistruct ChildItem {
150e41f4b71Sopenharmony_ci  @Prop item: string;
151e41f4b71Sopenharmony_ci
152e41f4b71Sopenharmony_ci  build() {
153e41f4b71Sopenharmony_ci    Text(this.item)
154e41f4b71Sopenharmony_ci      .fontSize(30)
155e41f4b71Sopenharmony_ci  }
156e41f4b71Sopenharmony_ci}
157e41f4b71Sopenharmony_ci```
158e41f4b71Sopenharmony_ci
159e41f4b71Sopenharmony_ciThe figure below shows the effect.
160e41f4b71Sopenharmony_ci
161e41f4b71Sopenharmony_ci**Figure 4** Re-rendering with ForEach 
162e41f4b71Sopenharmony_ci![ForEach-Non-Initial-Render-Case-Effect](figures/ForEach-Non-Initial-Render-Case-Effect.gif)
163e41f4b71Sopenharmony_ci
164e41f4b71Sopenharmony_ciFrom this example, you can see that @State can observe changes in the primitive array items of the **simpleList** data source.
165e41f4b71Sopenharmony_ci
166e41f4b71Sopenharmony_ci1. When any array item in **simpleList** changes, **ForEach** is triggered for re-rendering.
167e41f4b71Sopenharmony_ci2. **ForEach** traverses the new data source **['one', 'two', 'new three']** and generates the corresponding keys **one**, **two**, and **new three**.
168e41f4b71Sopenharmony_ci3. 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 new component for it.
169e41f4b71Sopenharmony_ci
170e41f4b71Sopenharmony_ci## Use Cases
171e41f4b71Sopenharmony_ci
172e41f4b71Sopenharmony_ci**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).
173e41f4b71Sopenharmony_ci
174e41f4b71Sopenharmony_ci### Data Source Unchanged
175e41f4b71Sopenharmony_ci
176e41f4b71Sopenharmony_ciIf 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.
177e41f4b71Sopenharmony_ci
178e41f4b71Sopenharmony_ci```ts
179e41f4b71Sopenharmony_ci@Entry
180e41f4b71Sopenharmony_ci@Component
181e41f4b71Sopenharmony_cistruct ArticleList {
182e41f4b71Sopenharmony_ci  @State simpleList: Array<number> = [1, 2, 3, 4, 5];
183e41f4b71Sopenharmony_ci
184e41f4b71Sopenharmony_ci  build() {
185e41f4b71Sopenharmony_ci    Column() {
186e41f4b71Sopenharmony_ci      ForEach(this.simpleList, (item: number) => {
187e41f4b71Sopenharmony_ci        ArticleSkeletonView()
188e41f4b71Sopenharmony_ci          .margin({ top: 20 })
189e41f4b71Sopenharmony_ci      }, (item: number) => item.toString())
190e41f4b71Sopenharmony_ci    }
191e41f4b71Sopenharmony_ci    .padding(20)
192e41f4b71Sopenharmony_ci    .width('100%')
193e41f4b71Sopenharmony_ci    .height('100%')
194e41f4b71Sopenharmony_ci  }
195e41f4b71Sopenharmony_ci}
196e41f4b71Sopenharmony_ci
197e41f4b71Sopenharmony_ci@Builder
198e41f4b71Sopenharmony_cifunction textArea(width: number | Resource | string = '100%', height: number | Resource | string = '100%') {
199e41f4b71Sopenharmony_ci  Row()
200e41f4b71Sopenharmony_ci    .width(width)
201e41f4b71Sopenharmony_ci    .height(height)
202e41f4b71Sopenharmony_ci    .backgroundColor('#FFF2F3F4')
203e41f4b71Sopenharmony_ci}
204e41f4b71Sopenharmony_ci
205e41f4b71Sopenharmony_ci@Component
206e41f4b71Sopenharmony_cistruct ArticleSkeletonView {
207e41f4b71Sopenharmony_ci  build() {
208e41f4b71Sopenharmony_ci    Row() {
209e41f4b71Sopenharmony_ci      Column() {
210e41f4b71Sopenharmony_ci        textArea(80, 80)
211e41f4b71Sopenharmony_ci      }
212e41f4b71Sopenharmony_ci      .margin({ right: 20 })
213e41f4b71Sopenharmony_ci
214e41f4b71Sopenharmony_ci      Column() {
215e41f4b71Sopenharmony_ci        textArea('60%', 20)
216e41f4b71Sopenharmony_ci        textArea('50%', 20)
217e41f4b71Sopenharmony_ci      }
218e41f4b71Sopenharmony_ci      .alignItems(HorizontalAlign.Start)
219e41f4b71Sopenharmony_ci      .justifyContent(FlexAlign.SpaceAround)
220e41f4b71Sopenharmony_ci      .height('100%')
221e41f4b71Sopenharmony_ci    }
222e41f4b71Sopenharmony_ci    .padding(20)
223e41f4b71Sopenharmony_ci    .borderRadius(12)
224e41f4b71Sopenharmony_ci    .backgroundColor('#FFECECEC')
225e41f4b71Sopenharmony_ci    .height(120)
226e41f4b71Sopenharmony_ci    .width('100%')
227e41f4b71Sopenharmony_ci    .justifyContent(FlexAlign.SpaceBetween)
228e41f4b71Sopenharmony_ci  }
229e41f4b71Sopenharmony_ci}
230e41f4b71Sopenharmony_ci```
231e41f4b71Sopenharmony_ci
232e41f4b71Sopenharmony_ciThe figure below shows the effect.
233e41f4b71Sopenharmony_ci
234e41f4b71Sopenharmony_ci**Figure 5** Skeleton screen 
235e41f4b71Sopenharmony_ci![ForEach-SkeletonScreen](figures/ForEach-SkeletonScreen.png)
236e41f4b71Sopenharmony_ci
237e41f4b71Sopenharmony_ciIn this example, the data item **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.
238e41f4b71Sopenharmony_ci
239e41f4b71Sopenharmony_ci### Data Source Changed
240e41f4b71Sopenharmony_ci
241e41f4b71Sopenharmony_ciIf 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.
242e41f4b71Sopenharmony_ci
243e41f4b71Sopenharmony_ci```ts
244e41f4b71Sopenharmony_ciclass Article {
245e41f4b71Sopenharmony_ci  id: string;
246e41f4b71Sopenharmony_ci  title: string;
247e41f4b71Sopenharmony_ci  brief: string;
248e41f4b71Sopenharmony_ci
249e41f4b71Sopenharmony_ci  constructor(id: string, title: string, brief: string) {
250e41f4b71Sopenharmony_ci    this.id = id;
251e41f4b71Sopenharmony_ci    this.title = title;
252e41f4b71Sopenharmony_ci    this.brief = brief;
253e41f4b71Sopenharmony_ci  }
254e41f4b71Sopenharmony_ci}
255e41f4b71Sopenharmony_ci
256e41f4b71Sopenharmony_ci@Entry
257e41f4b71Sopenharmony_ci@Component
258e41f4b71Sopenharmony_cistruct ArticleListView {
259e41f4b71Sopenharmony_ci  @State isListReachEnd: boolean = false;
260e41f4b71Sopenharmony_ci  @State articleList: Array<Article> = [
261e41f4b71Sopenharmony_ci    new Article('001','Article 1','Abstract'),
262e41f4b71Sopenharmony_ci    new Article('002','Article 2','Abstract'),
263e41f4b71Sopenharmony_ci    new Article('003','Article 3','Abstract'),
264e41f4b71Sopenharmony_ci    new Article('004','Article 4','Abstract'),
265e41f4b71Sopenharmony_ci    new Article('005','Article 5','Abstract'),
266e41f4b71Sopenharmony_ci    new Article ('006','Article 6','Abstract')
267e41f4b71Sopenharmony_ci  ]
268e41f4b71Sopenharmony_ci
269e41f4b71Sopenharmony_ci  loadMoreArticles() {
270e41f4b71Sopenharmony_ci    this.articleList.push(new Article('007','New article','Abstract');
271e41f4b71Sopenharmony_ci  }
272e41f4b71Sopenharmony_ci
273e41f4b71Sopenharmony_ci  build() {
274e41f4b71Sopenharmony_ci    Column({ space: 5 }) {
275e41f4b71Sopenharmony_ci      List() {
276e41f4b71Sopenharmony_ci        ForEach(this.articleList, (item: Article) => {
277e41f4b71Sopenharmony_ci          ListItem() {
278e41f4b71Sopenharmony_ci            ArticleCard({ article: item })
279e41f4b71Sopenharmony_ci              .margin({ top: 20 })
280e41f4b71Sopenharmony_ci          }
281e41f4b71Sopenharmony_ci        }, (item: Article) => item.id)
282e41f4b71Sopenharmony_ci      }
283e41f4b71Sopenharmony_ci      .onReachEnd(() => {
284e41f4b71Sopenharmony_ci        this.isListReachEnd = true;
285e41f4b71Sopenharmony_ci      })
286e41f4b71Sopenharmony_ci      .parallelGesture(
287e41f4b71Sopenharmony_ci        PanGesture({ direction: PanDirection.Up, distance: 80 })
288e41f4b71Sopenharmony_ci          .onActionStart(() => {
289e41f4b71Sopenharmony_ci            if (this.isListReachEnd) {
290e41f4b71Sopenharmony_ci              this.loadMoreArticles();
291e41f4b71Sopenharmony_ci              this.isListReachEnd = false;
292e41f4b71Sopenharmony_ci            }
293e41f4b71Sopenharmony_ci          })
294e41f4b71Sopenharmony_ci      )
295e41f4b71Sopenharmony_ci      .padding(20)
296e41f4b71Sopenharmony_ci      .scrollBar(BarState.Off)
297e41f4b71Sopenharmony_ci    }
298e41f4b71Sopenharmony_ci    .width('100%')
299e41f4b71Sopenharmony_ci    .height('100%')
300e41f4b71Sopenharmony_ci    .backgroundColor(0xF1F3F5)
301e41f4b71Sopenharmony_ci  }
302e41f4b71Sopenharmony_ci}
303e41f4b71Sopenharmony_ci
304e41f4b71Sopenharmony_ci@Component
305e41f4b71Sopenharmony_cistruct ArticleCard {
306e41f4b71Sopenharmony_ci  @Prop article: Article;
307e41f4b71Sopenharmony_ci
308e41f4b71Sopenharmony_ci  build() {
309e41f4b71Sopenharmony_ci    Row() {
310e41f4b71Sopenharmony_ci      Image($r('app.media.icon'))
311e41f4b71Sopenharmony_ci        .width(80)
312e41f4b71Sopenharmony_ci        .height(80)
313e41f4b71Sopenharmony_ci        .margin({ right: 20 })
314e41f4b71Sopenharmony_ci
315e41f4b71Sopenharmony_ci      Column() {
316e41f4b71Sopenharmony_ci        Text(this.article.title)
317e41f4b71Sopenharmony_ci          .fontSize(20)
318e41f4b71Sopenharmony_ci          .margin({ bottom: 8 })
319e41f4b71Sopenharmony_ci        Text(this.article.brief)
320e41f4b71Sopenharmony_ci          .fontSize(16)
321e41f4b71Sopenharmony_ci          .fontColor(Color.Gray)
322e41f4b71Sopenharmony_ci          .margin({ bottom: 8 })
323e41f4b71Sopenharmony_ci      }
324e41f4b71Sopenharmony_ci      .alignItems(HorizontalAlign.Start)
325e41f4b71Sopenharmony_ci      .width('80%')
326e41f4b71Sopenharmony_ci      .height('100%')
327e41f4b71Sopenharmony_ci    }
328e41f4b71Sopenharmony_ci    .padding(20)
329e41f4b71Sopenharmony_ci    .borderRadius(12)
330e41f4b71Sopenharmony_ci    .backgroundColor('#FFECECEC')
331e41f4b71Sopenharmony_ci    .height(120)
332e41f4b71Sopenharmony_ci    .width('100%')
333e41f4b71Sopenharmony_ci    .justifyContent(FlexAlign.SpaceBetween)
334e41f4b71Sopenharmony_ci  }
335e41f4b71Sopenharmony_ci}
336e41f4b71Sopenharmony_ci```
337e41f4b71Sopenharmony_ci
338e41f4b71Sopenharmony_ciThe following figure shows the initial screen (on the left) and the screen after a pull-to-refresh gesture (on the right).
339e41f4b71Sopenharmony_ci
340e41f4b71Sopenharmony_ci**Figure 6** When the data source is changed 
341e41f4b71Sopenharmony_ci![ForEach-DataSourceArrayChange](figures/ForEach-DataSourceArrayChange.png)
342e41f4b71Sopenharmony_ci
343e41f4b71Sopenharmony_ciIn 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.
344e41f4b71Sopenharmony_ci
345e41f4b71Sopenharmony_ci1. When the list scrolls to the bottom, if the distance of finger movement exceeds the threshold 80, the **loadMoreArticle()** 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.
346e41f4b71Sopenharmony_ci2. 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.
347e41f4b71Sopenharmony_ci
348e41f4b71Sopenharmony_ci### Properties of Data Source Array Items Changed
349e41f4b71Sopenharmony_ci
350e41f4b71Sopenharmony_ciIf 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.
351e41f4b71Sopenharmony_ci
352e41f4b71Sopenharmony_ci```ts
353e41f4b71Sopenharmony_ci@Observed
354e41f4b71Sopenharmony_ciclass Article {
355e41f4b71Sopenharmony_ci  id: string;
356e41f4b71Sopenharmony_ci  title: string;
357e41f4b71Sopenharmony_ci  brief: string;
358e41f4b71Sopenharmony_ci  isLiked: boolean;
359e41f4b71Sopenharmony_ci  likesCount: number;
360e41f4b71Sopenharmony_ci
361e41f4b71Sopenharmony_ci  constructor(id: string, title: string, brief: string, isLiked: boolean, likesCount: number) {
362e41f4b71Sopenharmony_ci    this.id = id;
363e41f4b71Sopenharmony_ci    this.title = title;
364e41f4b71Sopenharmony_ci    this.brief = brief;
365e41f4b71Sopenharmony_ci    this.isLiked = isLiked;
366e41f4b71Sopenharmony_ci    this.likesCount = likesCount;
367e41f4b71Sopenharmony_ci  }
368e41f4b71Sopenharmony_ci}
369e41f4b71Sopenharmony_ci
370e41f4b71Sopenharmony_ci@Entry
371e41f4b71Sopenharmony_ci@Component
372e41f4b71Sopenharmony_cistruct ArticleListView {
373e41f4b71Sopenharmony_ci  @State articleList: Array<Article> = [
374e41f4b71Sopenharmony_ci    new Article('001','Article 0','Abstract', false, 100),
375e41f4b71Sopenharmony_ci    new Article('002','Article 1','Abstract', false, 100),
376e41f4b71Sopenharmony_ci    new Article('003','Article 2','Abstract', false, 100),
377e41f4b71Sopenharmony_ci    new Article('004','Article 4','Abstract', false, 100),
378e41f4b71Sopenharmony_ci    new Article('005','Article 5','Abstract', false, 100),
379e41f4b71Sopenharmony_ci    new Article('006','Article 6','Abstract', false, 100),
380e41f4b71Sopenharmony_ci  ];
381e41f4b71Sopenharmony_ci
382e41f4b71Sopenharmony_ci  build() {
383e41f4b71Sopenharmony_ci    List() {
384e41f4b71Sopenharmony_ci      ForEach(this.articleList, (item: Article) => {
385e41f4b71Sopenharmony_ci        ListItem() {
386e41f4b71Sopenharmony_ci          ArticleCard({
387e41f4b71Sopenharmony_ci            article: item
388e41f4b71Sopenharmony_ci          })
389e41f4b71Sopenharmony_ci            .margin({ top: 20 })
390e41f4b71Sopenharmony_ci        }
391e41f4b71Sopenharmony_ci      }, (item: Article) => item.id)
392e41f4b71Sopenharmony_ci    }
393e41f4b71Sopenharmony_ci    .padding(20)
394e41f4b71Sopenharmony_ci    .scrollBar(BarState.Off)
395e41f4b71Sopenharmony_ci    .backgroundColor(0xF1F3F5)
396e41f4b71Sopenharmony_ci  }
397e41f4b71Sopenharmony_ci}
398e41f4b71Sopenharmony_ci
399e41f4b71Sopenharmony_ci@Component
400e41f4b71Sopenharmony_cistruct ArticleCard {
401e41f4b71Sopenharmony_ci  @ObjectLink article: Article;
402e41f4b71Sopenharmony_ci
403e41f4b71Sopenharmony_ci  handleLiked() {
404e41f4b71Sopenharmony_ci    this.article.isLiked = !this.article.isLiked;
405e41f4b71Sopenharmony_ci    this.article.likesCount = this.article.isLiked ? this.article.likesCount + 1 : this.article.likesCount - 1;
406e41f4b71Sopenharmony_ci  }
407e41f4b71Sopenharmony_ci
408e41f4b71Sopenharmony_ci  build() {
409e41f4b71Sopenharmony_ci    Row() {
410e41f4b71Sopenharmony_ci      Image($r('app.media.icon'))
411e41f4b71Sopenharmony_ci        .width(80)
412e41f4b71Sopenharmony_ci        .height(80)
413e41f4b71Sopenharmony_ci        .margin({ right: 20 })
414e41f4b71Sopenharmony_ci
415e41f4b71Sopenharmony_ci      Column() {
416e41f4b71Sopenharmony_ci        Text(this.article.title)
417e41f4b71Sopenharmony_ci          .fontSize(20)
418e41f4b71Sopenharmony_ci          .margin({ bottom: 8 })
419e41f4b71Sopenharmony_ci        Text(this.article.brief)
420e41f4b71Sopenharmony_ci          .fontSize(16)
421e41f4b71Sopenharmony_ci          .fontColor(Color.Gray)
422e41f4b71Sopenharmony_ci          .margin({ bottom: 8 })
423e41f4b71Sopenharmony_ci
424e41f4b71Sopenharmony_ci        Row() {
425e41f4b71Sopenharmony_ci          Image(this.article.isLiked ? $r('app.media.iconLiked') : $r('app.media.iconUnLiked'))
426e41f4b71Sopenharmony_ci            .width(24)
427e41f4b71Sopenharmony_ci            .height(24)
428e41f4b71Sopenharmony_ci            .margin({ right: 8 })
429e41f4b71Sopenharmony_ci          Text(this.article.likesCount.toString())
430e41f4b71Sopenharmony_ci            .fontSize(16)
431e41f4b71Sopenharmony_ci        }
432e41f4b71Sopenharmony_ci        .onClick(() => this.handleLiked())
433e41f4b71Sopenharmony_ci        .justifyContent(FlexAlign.Center)
434e41f4b71Sopenharmony_ci      }
435e41f4b71Sopenharmony_ci      .alignItems(HorizontalAlign.Start)
436e41f4b71Sopenharmony_ci      .width('80%')
437e41f4b71Sopenharmony_ci      .height('100%')
438e41f4b71Sopenharmony_ci    }
439e41f4b71Sopenharmony_ci    .padding(20)
440e41f4b71Sopenharmony_ci    .borderRadius(12)
441e41f4b71Sopenharmony_ci    .backgroundColor('#FFECECEC')
442e41f4b71Sopenharmony_ci    .height(120)
443e41f4b71Sopenharmony_ci    .width('100%')
444e41f4b71Sopenharmony_ci    .justifyContent(FlexAlign.SpaceBetween)
445e41f4b71Sopenharmony_ci  }
446e41f4b71Sopenharmony_ci}
447e41f4b71Sopenharmony_ci```
448e41f4b71Sopenharmony_ci
449e41f4b71Sopenharmony_ciThe following figure shows the initial screen (on the left) and the screen after the Like icon of Article 1 is clicked (on the right).
450e41f4b71Sopenharmony_ci
451e41f4b71Sopenharmony_ci**Figure 7** When properties of data source array items are changed 
452e41f4b71Sopenharmony_ci![ForEach-DataSourceArraySubpropertyChange](figures/ForEach-DataSourceArraySubpropertyChange.png)
453e41f4b71Sopenharmony_ci
454e41f4b71Sopenharmony_ciIn 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.
455e41f4b71Sopenharmony_ci
456e41f4b71Sopenharmony_ci1. 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.
457e41f4b71Sopenharmony_ci2. Because **article** in the child component **ArticleCard** uses the @ObjectLink decorator, the parent and child components share the same article data. As such, the values of **isLiked** and **likedCounts** of the first array item of **articleList** in the parent component are changed synchronously.
458e41f4b71Sopenharmony_ci3. When the parent component detects property changes of the data source array items, **ForEach** is triggered for re-rendering.
459e41f4b71Sopenharmony_ci4. Here, the **ForEach** key generation rule is the **id** property value of the array item. If **ForEach** traverses the new data source and finds no changes in the **id** values, no component is created.
460e41f4b71Sopenharmony_ci5. When the **ArticleCard** component corresponding to the first array item is rendered, the obtained values of **isLiked** and** likesCount** are the new values.
461e41f4b71Sopenharmony_ci
462e41f4b71Sopenharmony_ci### Enabling Drag and Sort
463e41f4b71Sopenharmony_ciIf **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.
464e41f4b71Sopenharmony_ci
465e41f4b71Sopenharmony_ci```ts
466e41f4b71Sopenharmony_ci@Entry
467e41f4b71Sopenharmony_ci@Component
468e41f4b71Sopenharmony_cistruct ForEachSort {
469e41f4b71Sopenharmony_ci  @State arr: Array<string> = [];
470e41f4b71Sopenharmony_ci
471e41f4b71Sopenharmony_ci  build() {
472e41f4b71Sopenharmony_ci    Row() {
473e41f4b71Sopenharmony_ci      List() {
474e41f4b71Sopenharmony_ci        ForEach(this.arr, (item: string) => {
475e41f4b71Sopenharmony_ci          ListItem() {
476e41f4b71Sopenharmony_ci            Text(item.toString())
477e41f4b71Sopenharmony_ci              .fontSize(16)
478e41f4b71Sopenharmony_ci              .textAlign(TextAlign.Center)
479e41f4b71Sopenharmony_ci              .size({height: 100, width: "100%"})
480e41f4b71Sopenharmony_ci          }.margin(10)
481e41f4b71Sopenharmony_ci          .borderRadius(10)
482e41f4b71Sopenharmony_ci          .backgroundColor("#FFFFFFFF")
483e41f4b71Sopenharmony_ci        }, (item: string) => item)
484e41f4b71Sopenharmony_ci          .onMove((from:number, to:number) => {
485e41f4b71Sopenharmony_ci            let tmp = this.arr.splice(from, 1);
486e41f4b71Sopenharmony_ci            this.arr.splice(to, 0, tmp[0])
487e41f4b71Sopenharmony_ci          })
488e41f4b71Sopenharmony_ci      }
489e41f4b71Sopenharmony_ci      .width('100%')
490e41f4b71Sopenharmony_ci      .height('100%')
491e41f4b71Sopenharmony_ci      .backgroundColor("#FFDCDCDC")
492e41f4b71Sopenharmony_ci    }
493e41f4b71Sopenharmony_ci  }
494e41f4b71Sopenharmony_ci  aboutToAppear(): void {
495e41f4b71Sopenharmony_ci    for (let i = 0; i < 100; i++) {
496e41f4b71Sopenharmony_ci      this.arr.push(i.toString())
497e41f4b71Sopenharmony_ci    }
498e41f4b71Sopenharmony_ci  }
499e41f4b71Sopenharmony_ci}
500e41f4b71Sopenharmony_ci```
501e41f4b71Sopenharmony_ci
502e41f4b71Sopenharmony_ci**Figure 8** Drag and sort in ForEach 
503e41f4b71Sopenharmony_ci![ForEach-Drag-Sort](figures/ForEach-Drag-Sort.gif)
504e41f4b71Sopenharmony_ci## Suggestions
505e41f4b71Sopenharmony_ci
506e41f4b71Sopenharmony_ci- To ensure unique keys for array items of the Object type, you are advised to use the unique IDs of objects as keys.
507e41f4b71Sopenharmony_ci- 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.
508e41f4b71Sopenharmony_ci- 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 **id** property as the key generation rule.
509e41f4b71Sopenharmony_ci- 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.
510e41f4b71Sopenharmony_ci
511e41f4b71Sopenharmony_ci## Common Pitfalls
512e41f4b71Sopenharmony_ci
513e41f4b71Sopenharmony_ciCorrect 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).
514e41f4b71Sopenharmony_ci
515e41f4b71Sopenharmony_ci### Rendering Result Not as Expected
516e41f4b71Sopenharmony_ci
517e41f4b71Sopenharmony_ciIn 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.
518e41f4b71Sopenharmony_ci
519e41f4b71Sopenharmony_ci```ts
520e41f4b71Sopenharmony_ci@Entry
521e41f4b71Sopenharmony_ci@Component
522e41f4b71Sopenharmony_cistruct Parent {
523e41f4b71Sopenharmony_ci  @State simpleList: Array<string> = ['one', 'two', 'three'];
524e41f4b71Sopenharmony_ci
525e41f4b71Sopenharmony_ci  build() {
526e41f4b71Sopenharmony_ci    Column() {
527e41f4b71Sopenharmony_ci      Button() {
528e41f4b71Sopenharmony_ci        Text('Insert Item After First Item').fontSize(30)
529e41f4b71Sopenharmony_ci      }
530e41f4b71Sopenharmony_ci      .onClick(() => {
531e41f4b71Sopenharmony_ci        this.simpleList.splice(1, 0, 'new item');
532e41f4b71Sopenharmony_ci      })
533e41f4b71Sopenharmony_ci
534e41f4b71Sopenharmony_ci      ForEach(this.simpleList, (item: string) => {
535e41f4b71Sopenharmony_ci        ChildItem({ item: item })
536e41f4b71Sopenharmony_ci      }, (item: string, index: number) => index.toString())
537e41f4b71Sopenharmony_ci    }
538e41f4b71Sopenharmony_ci    .justifyContent(FlexAlign.Center)
539e41f4b71Sopenharmony_ci    .width('100%')
540e41f4b71Sopenharmony_ci    .height('100%')
541e41f4b71Sopenharmony_ci    .backgroundColor(0xF1F3F5)
542e41f4b71Sopenharmony_ci  }
543e41f4b71Sopenharmony_ci}
544e41f4b71Sopenharmony_ci
545e41f4b71Sopenharmony_ci@Component
546e41f4b71Sopenharmony_cistruct ChildItem {
547e41f4b71Sopenharmony_ci  @Prop item: string;
548e41f4b71Sopenharmony_ci
549e41f4b71Sopenharmony_ci  build() {
550e41f4b71Sopenharmony_ci    Text(this.item)
551e41f4b71Sopenharmony_ci      .fontSize(30)
552e41f4b71Sopenharmony_ci  }
553e41f4b71Sopenharmony_ci}
554e41f4b71Sopenharmony_ci```
555e41f4b71Sopenharmony_ci
556e41f4b71Sopenharmony_ciThe following figure shows the initial screen and the screen after **Insert Item After First Item** is clicked.
557e41f4b71Sopenharmony_ci
558e41f4b71Sopenharmony_ci**Figure 9** Rendering result not as expected  
559e41f4b71Sopenharmony_ci![ForEach-UnexpectedRenderingResult](figures/ForEach-UnexpectedRenderingResult.gif)
560e41f4b71Sopenharmony_ci
561e41f4b71Sopenharmony_ciWhen **ForEach** is used for initial rendering, the created keys are **0**, **1**, and **2** in sequence.
562e41f4b71Sopenharmony_ci
563e41f4b71Sopenharmony_ciAfter 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.
564e41f4b71Sopenharmony_ci
565e41f4b71Sopenharmony_ci**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.
566e41f4b71Sopenharmony_ci
567e41f4b71Sopenharmony_ciIn 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**.
568e41f4b71Sopenharmony_ci
569e41f4b71Sopenharmony_ci### Deteriorated Rendering Performance
570e41f4b71Sopenharmony_ci
571e41f4b71Sopenharmony_ciIn 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.
572e41f4b71Sopenharmony_ci
573e41f4b71Sopenharmony_ci```ts
574e41f4b71Sopenharmony_ci@Entry
575e41f4b71Sopenharmony_ci@Component
576e41f4b71Sopenharmony_cistruct Parent {
577e41f4b71Sopenharmony_ci  @State simpleList: Array<string> = ['one', 'two', 'three'];
578e41f4b71Sopenharmony_ci
579e41f4b71Sopenharmony_ci  build() {
580e41f4b71Sopenharmony_ci    Column() {
581e41f4b71Sopenharmony_ci      Button() {
582e41f4b71Sopenharmony_ci        Text('Insert Item After First Item').fontSize(30)
583e41f4b71Sopenharmony_ci      }
584e41f4b71Sopenharmony_ci      .onClick(() => {
585e41f4b71Sopenharmony_ci        this.simpleList.splice(1, 0, 'new item');
586e41f4b71Sopenharmony_ci        console.log(`[onClick]: simpleList is ${JSON.stringify(this.simpleList)}`);
587e41f4b71Sopenharmony_ci      })
588e41f4b71Sopenharmony_ci
589e41f4b71Sopenharmony_ci      ForEach(this.simpleList, (item: string) => {
590e41f4b71Sopenharmony_ci        ChildItem({ item: item })
591e41f4b71Sopenharmony_ci      })
592e41f4b71Sopenharmony_ci    }
593e41f4b71Sopenharmony_ci    .justifyContent(FlexAlign.Center)
594e41f4b71Sopenharmony_ci    .width('100%')
595e41f4b71Sopenharmony_ci    .height('100%')
596e41f4b71Sopenharmony_ci    .backgroundColor(0xF1F3F5)
597e41f4b71Sopenharmony_ci  }
598e41f4b71Sopenharmony_ci}
599e41f4b71Sopenharmony_ci
600e41f4b71Sopenharmony_ci@Component
601e41f4b71Sopenharmony_cistruct ChildItem {
602e41f4b71Sopenharmony_ci  @Prop item: string;
603e41f4b71Sopenharmony_ci
604e41f4b71Sopenharmony_ci  aboutToAppear() {
605e41f4b71Sopenharmony_ci    console.log(`[aboutToAppear]: item is ${this.item}`);
606e41f4b71Sopenharmony_ci  }
607e41f4b71Sopenharmony_ci
608e41f4b71Sopenharmony_ci  build() {
609e41f4b71Sopenharmony_ci    Text(this.item)
610e41f4b71Sopenharmony_ci      .fontSize(50)
611e41f4b71Sopenharmony_ci  }
612e41f4b71Sopenharmony_ci}
613e41f4b71Sopenharmony_ci```
614e41f4b71Sopenharmony_ci
615e41f4b71Sopenharmony_ciThe following figure shows the initial screen and the screen after **Insert Item After First Item** is clicked.
616e41f4b71Sopenharmony_ci
617e41f4b71Sopenharmony_ci**Figure 10** Deteriorated rendering performance 
618e41f4b71Sopenharmony_ci![ForEach-RenderPerformanceDecrease](figures/ForEach-RenderPerformanceDecrease.gif)
619e41f4b71Sopenharmony_ci
620e41f4b71Sopenharmony_ciAfter **Insert Item After First Item** is clicked, DevEco Studio displays logs as shown in the figure below.
621e41f4b71Sopenharmony_ci
622e41f4b71Sopenharmony_ci**Figure 11** Logs indicating deteriorated rendering performance 
623e41f4b71Sopenharmony_ci![ForEach-RenderPerformanceDecreaseLogs](figures/ForEach-RenderPerformanceDecreaseLogs.png)
624e41f4b71Sopenharmony_ci
625e41f4b71Sopenharmony_ciAfter a new item is inserted, **ForEach** creates the corresponding child items for the **new item**, **two**, and **three** array items, and executes the [aboutToAppear()](https://gitee.com/openharmony/docs/blob/master/en/application-dev/reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#abouttoappear) callback. Below are the reasons:
626e41f4b71Sopenharmony_ci
627e41f4b71Sopenharmony_ci1. When **ForEach** is used for initial rendering, the created keys are **0__one**, **1__two** and **2__three** in sequence.
628e41f4b71Sopenharmony_ci2. 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.
629e41f4b71Sopenharmony_ci3. **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.
630e41f4b71Sopenharmony_ci
631e41f4b71Sopenharmony_ciAlthough 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 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.
632