1e41f4b71Sopenharmony_ci# ForEach:循环渲染
2e41f4b71Sopenharmony_ci
3e41f4b71Sopenharmony_ciAPI参数说明见:[ForEach API参数说明](../reference/apis-arkui/arkui-ts/ts-rendering-control-foreach.md)
4e41f4b71Sopenharmony_ci
5e41f4b71Sopenharmony_ciForEach接口基于数组类型数据来进行循环渲染,需要与容器组件配合使用,且接口返回的组件应当是允许包含在ForEach父容器组件中的子组件。例如,ListItem组件要求ForEach的父容器组件必须为[List组件](../reference/apis-arkui/arkui-ts/ts-container-list.md)。
6e41f4b71Sopenharmony_ci
7e41f4b71Sopenharmony_ci> **说明:**
8e41f4b71Sopenharmony_ci>
9e41f4b71Sopenharmony_ci> 从API version 9开始,该接口支持在ArkTS卡片中使用。
10e41f4b71Sopenharmony_ci
11e41f4b71Sopenharmony_ci## 键值生成规则
12e41f4b71Sopenharmony_ci
13e41f4b71Sopenharmony_ci在`ForEach`循环渲染过程中,系统会为每个数组元素生成一个唯一且持久的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。
14e41f4b71Sopenharmony_ci
15e41f4b71Sopenharmony_ci`ForEach`提供了一个名为`keyGenerator`的参数,这是一个函数,开发者可以通过它自定义键值的生成规则。如果开发者没有定义`keyGenerator`函数,则ArkUI框架会使用默认的键值生成函数,即`(item: Object, index: number) => { return index + '__' + JSON.stringify(item); }`。
16e41f4b71Sopenharmony_ci
17e41f4b71Sopenharmony_ciArkUI框架对于`ForEach`的键值生成有一套特定的判断规则,这主要与`itemGenerator`函数的第二个参数`index`以及`keyGenerator`函数的第二个参数`index`有关,具体的键值生成规则判断逻辑如下图所示。
18e41f4b71Sopenharmony_ci
19e41f4b71Sopenharmony_ci**图1** ForEach键值生成规则  
20e41f4b71Sopenharmony_ci![ForEach-Key-Generation-Rules](figures/ForEach-Key-Generation-Rules.png)
21e41f4b71Sopenharmony_ci
22e41f4b71Sopenharmony_ci> **说明:**
23e41f4b71Sopenharmony_ci>
24e41f4b71Sopenharmony_ci> ArkUI框架会对重复的键值发出警告。在UI更新的场景下,如果出现重复的键值,框架可能无法正常工作,具体请参见[渲染结果非预期](#渲染结果非预期)。
25e41f4b71Sopenharmony_ci
26e41f4b71Sopenharmony_ci## 组件创建规则
27e41f4b71Sopenharmony_ci
28e41f4b71Sopenharmony_ci在确定键值生成规则后,ForEach的第二个参数`itemGenerator`函数会根据键值生成规则为数据源的每个数组项创建组件。组件的创建包括两种情况:[ForEach首次渲染](#首次渲染)和[ForEach非首次渲染](#非首次渲染)。
29e41f4b71Sopenharmony_ci
30e41f4b71Sopenharmony_ci### 首次渲染
31e41f4b71Sopenharmony_ci
32e41f4b71Sopenharmony_ci在ForEach首次渲染时,会根据前述键值生成规则为数据源的每个数组项生成唯一键值,并创建相应的组件。
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_ci运行效果如下图所示。
67e41f4b71Sopenharmony_ci
68e41f4b71Sopenharmony_ci**图2**  ForEach数据源不存在相同值案例首次渲染运行效果图  
69e41f4b71Sopenharmony_ci![ForEach-CaseStudy-1stRender-NoDup](figures/ForEach-CaseStudy-1stRender-NoDup.png)
70e41f4b71Sopenharmony_ci
71e41f4b71Sopenharmony_ci在上述代码中,键值生成规则是`keyGenerator`函数的返回值`item`。在ForEach渲染循环时,为数据源数组项依次生成键值`one`、`two`和`three`,并创建对应的`ChildItem`组件渲染到界面上。
72e41f4b71Sopenharmony_ci
73e41f4b71Sopenharmony_ci当不同数组项按照键值生成规则生成的键值相同时,框架的行为是未定义的。例如,在以下代码中,ForEach渲染相同的数据项`two`时,只创建了一个`ChildItem`组件,而没有创建多个具有相同键值的组件。
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_ci运行效果如下图所示。
108e41f4b71Sopenharmony_ci
109e41f4b71Sopenharmony_ci**图3**  ForEach数据源存在相同值案例首次渲染运行效果图  
110e41f4b71Sopenharmony_ci![ForEach-CaseStudy-1stRender-Dup](figures/ForEach-CaseStudy-1stRender-Dup.png)
111e41f4b71Sopenharmony_ci
112e41f4b71Sopenharmony_ci在该示例中,最终键值生成规则为`item`。当ForEach遍历数据源`simpleList`,遍历到索引为1的`two`时,按照最终键值生成规则生成键值为`two`的组件并进行标记。当遍历到索引为2的`two`时,按照最终键值生成规则当前项的键值也为`two`,此时不再创建新的组件。
113e41f4b71Sopenharmony_ci
114e41f4b71Sopenharmony_ci### 非首次渲染
115e41f4b71Sopenharmony_ci
116e41f4b71Sopenharmony_ci在ForEach组件进行非首次渲染时,它会检查新生成的键值是否在上次渲染中已经存在。如果键值不存在,则会创建一个新的组件;如果键值存在,则不会创建新的组件,而是直接渲染该键值所对应的组件。例如,在以下的代码示例中,通过点击事件修改了数组的第三项值为"new three",这将触发ForEach组件进行非首次渲染。
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('点击修改第3个数组项的值')
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_ci运行效果如下图所示。
160e41f4b71Sopenharmony_ci
161e41f4b71Sopenharmony_ci**图4**  ForEach非首次渲染案例运行效果图  
162e41f4b71Sopenharmony_ci![ForEach-Non-Initial-Render-Case-Effect](figures/ForEach-Non-Initial-Render-Case-Effect.gif)
163e41f4b71Sopenharmony_ci
164e41f4b71Sopenharmony_ci从本例可以看出`@State` 能够监听到简单数据类型数组数据源 `simpleList` 数组项的变化。
165e41f4b71Sopenharmony_ci
166e41f4b71Sopenharmony_ci1. 当 `simpleList` 数组项发生变化时,会触发 `ForEach` 进行重新渲染。
167e41f4b71Sopenharmony_ci2. `ForEach` 遍历新的数据源 `['one', 'two', 'new three']`,并生成对应的键值`one`、`two`和`new three`。
168e41f4b71Sopenharmony_ci3. 其中,键值`one`和`two`在上次渲染中已经存在,所以 `ForEach` 复用了对应的组件并进行了渲染。对于第三个数组项 "new three",由于其通过键值生成规则 `item` 生成的键值`new three`在上次渲染中不存在,因此 `ForEach` 为该数组项创建了一个新的组件。
169e41f4b71Sopenharmony_ci
170e41f4b71Sopenharmony_ci## 使用场景
171e41f4b71Sopenharmony_ci
172e41f4b71Sopenharmony_ciForEach组件在开发过程中的主要应用场景包括:[数据源不变](#数据源不变)、[数据源数组项发生变化](#数据源数组项发生变化)(如插入、删除操作)、[数据源数组项子属性变化](#数据源数组项子属性变化)。
173e41f4b71Sopenharmony_ci
174e41f4b71Sopenharmony_ci### 数据源不变
175e41f4b71Sopenharmony_ci
176e41f4b71Sopenharmony_ci在数据源保持不变的场景中,数据源可以直接采用基本数据类型。例如,在页面加载状态时,可以使用骨架屏列表进行渲染展示。
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_ci运行效果如下图所示。
233e41f4b71Sopenharmony_ci
234e41f4b71Sopenharmony_ci**图5** 骨架屏运行效果图  
235e41f4b71Sopenharmony_ci![ForEach-SkeletonScreen](figures/ForEach-SkeletonScreen.png)
236e41f4b71Sopenharmony_ci
237e41f4b71Sopenharmony_ci在本示例中,采用数据项item作为键值生成规则,由于数据源simpleList的数组项各不相同,因此能够保证键值的唯一性。
238e41f4b71Sopenharmony_ci
239e41f4b71Sopenharmony_ci### 数据源数组项发生变化
240e41f4b71Sopenharmony_ci
241e41f4b71Sopenharmony_ci在数据源数组项发生变化的场景下,例如进行数组插入、删除操作或者数组项索引位置发生交换时,数据源应为对象数组类型,并使用对象的唯一ID作为最终键值。例如,当在页面上通过手势上滑加载下一页数据时,会在数据源数组尾部新增新获取的数据项,从而使得数据源数组长度增大。
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', '第1篇文章', '文章简介内容'),
262e41f4b71Sopenharmony_ci    new Article('002', '第2篇文章', '文章简介内容'),
263e41f4b71Sopenharmony_ci    new Article('003', '第3篇文章', '文章简介内容'),
264e41f4b71Sopenharmony_ci    new Article('004', '第4篇文章', '文章简介内容'),
265e41f4b71Sopenharmony_ci    new Article('005', '第5篇文章', '文章简介内容'),
266e41f4b71Sopenharmony_ci    new Article('006', '第6篇文章', '文章简介内容')
267e41f4b71Sopenharmony_ci  ]
268e41f4b71Sopenharmony_ci
269e41f4b71Sopenharmony_ci  loadMoreArticles() {
270e41f4b71Sopenharmony_ci    this.articleList.push(new Article('007', '加载的新文章', '文章简介内容'));
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_ci初始运行效果(左图)和手势上滑加载后效果(右图)如下图所示。
339e41f4b71Sopenharmony_ci
340e41f4b71Sopenharmony_ci**图6**  数据源数组项变化案例运行效果图  
341e41f4b71Sopenharmony_ci![ForEach-DataSourceArrayChange](figures/ForEach-DataSourceArrayChange.png)
342e41f4b71Sopenharmony_ci
343e41f4b71Sopenharmony_ci在本示例中,`ArticleCard`组件作为`ArticleListView`组件的子组件,通过`@Prop`装饰器接收一个`Article`对象,用于渲染文章卡片。
344e41f4b71Sopenharmony_ci
345e41f4b71Sopenharmony_ci1. 当列表滚动到底部时,如果手势滑动距离超过指定的80,将触发`loadMoreArticle()`函数。此函数会在`articleList`数据源的尾部添加一个新的数据项,从而增加数据源的长度。
346e41f4b71Sopenharmony_ci2. 数据源被`@State`装饰器修饰,ArkUI框架能够感知到数据源长度的变化,并触发`ForEach`进行重新渲染。
347e41f4b71Sopenharmony_ci
348e41f4b71Sopenharmony_ci### 数据源数组项子属性变化
349e41f4b71Sopenharmony_ci
350e41f4b71Sopenharmony_ci当数据源的数组项为对象数据类型,并且只修改某个数组项的属性值时,由于数据源为复杂数据类型,ArkUI框架无法监听到`@State`装饰器修饰的数据源数组项的属性变化,从而无法触发`ForEach`的重新渲染。为实现`ForEach`重新渲染,需要结合`@Observed`和`@ObjectLink`装饰器使用。例如,在文章列表卡片上点击“点赞”按钮,从而修改文章的点赞数量。
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', '第0篇文章', '文章简介内容', false, 100),
375e41f4b71Sopenharmony_ci    new Article('002', '第1篇文章', '文章简介内容', false, 100),
376e41f4b71Sopenharmony_ci    new Article('003', '第2篇文章', '文章简介内容', false, 100),
377e41f4b71Sopenharmony_ci    new Article('004', '第4篇文章', '文章简介内容', false, 100),
378e41f4b71Sopenharmony_ci    new Article('005', '第5篇文章', '文章简介内容', false, 100),
379e41f4b71Sopenharmony_ci    new Article('006', '第6篇文章', '文章简介内容', 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_ci上述代码的初始运行效果(左图)和点击第1个文章卡片上的点赞图标后的运行效果(右图)如下图所示。
450e41f4b71Sopenharmony_ci
451e41f4b71Sopenharmony_ci**图7** 数据源数组项子属性变化案例运行效果图  
452e41f4b71Sopenharmony_ci![ForEach-DataSourceArraySubpropertyChange](figures/ForEach-DataSourceArraySubpropertyChange.png)
453e41f4b71Sopenharmony_ci
454e41f4b71Sopenharmony_ci在本示例中,`Article`类被`@Observed`装饰器修饰。父组件`ArticleListView`传入`Article`对象实例给子组件`ArticleCard`,子组件使用`@ObjectLink`装饰器接收该实例。
455e41f4b71Sopenharmony_ci
456e41f4b71Sopenharmony_ci1. 当点击第1个文章卡片上的点赞图标时,会触发`ArticleCard`组件的`handleLiked`函数。该函数修改第1个卡片对应组件里`article`实例的`isLiked`和`likesCount`属性值。
457e41f4b71Sopenharmony_ci2. 由于子组件`ArticleCard`中的`article`使用了`@ObjectLink`装饰器,父子组件共享同一份`article`数据。因此,父组件中`articleList`的第1个数组项的`isLiked`和`likedCounts`数值也会同步修改。
458e41f4b71Sopenharmony_ci3. 当父组件监听到数据源数组项属性值变化时,会触发`ForEach`重新渲染。
459e41f4b71Sopenharmony_ci4. 在此处,`ForEach`键值生成规则为数组项的`id`属性值。当`ForEach`遍历新数据源时,数组项的`id`均没有变化,不会新建组件。
460e41f4b71Sopenharmony_ci5. 渲染第1个数组项对应的`ArticleCard`组件时,读取到的`isLiked`和`likesCount`为修改后的新值。
461e41f4b71Sopenharmony_ci
462e41f4b71Sopenharmony_ci### 拖拽排序
463e41f4b71Sopenharmony_ci当ForEach在List组件下使用,并且设置了onMove事件,ForEach每次迭代都生成一个ListItem时,可以使能拖拽排序。拖拽排序离手后,如果数据位置发生变化,则会触发onMove事件,上报数据移动原始索引号和目标索引号。在onMove事件中,需要根据上报的起始索引号和目标索引号修改数据源。数据源修改前后,要保持每个数据的键值不变,只是顺序发生变化,才能保证落位动画正常执行。
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**图8** ForEach拖拽排序效果图  
503e41f4b71Sopenharmony_ci![ForEach-Drag-Sort](figures/ForEach-Drag-Sort.gif)
504e41f4b71Sopenharmony_ci## 使用建议
505e41f4b71Sopenharmony_ci
506e41f4b71Sopenharmony_ci- 为满足键值的唯一性,对于对象数据类型,建议使用对象数据中的唯一`id`作为键值。
507e41f4b71Sopenharmony_ci- 尽量避免在最终的键值生成规则中包含数据项索引`index`,以防止出现[渲染结果非预期](#渲染结果非预期)和[渲染性能降低](#渲染性能降低)。如果业务确实需要使用`index`,例如列表需要通过`index`进行条件渲染,开发者需要接受`ForEach`在改变数据源后重新创建组件所带来的性能损耗。
508e41f4b71Sopenharmony_ci- 基本数据类型的数据项没有唯一`ID`属性。如果使用基本数据类型本身作为键值,必须确保数组项无重复。因此,对于数据源会发生变化的场景,建议将基本数据类型数组转化为具备唯一`ID`属性的对象数据类型数组,再使用`ID`属性作为键值生成规则。
509e41f4b71Sopenharmony_ci- 对于以上限制规则,`index`参数存在的意义为:index是开发者保证键值唯一性的最终手段;对数据项进行修改时,由于`itemGenerator`中的`item`参数是不可修改的,所以须用index索引值对数据源进行修改,进而触发UI重新渲染。
510e41f4b71Sopenharmony_ci- ForEach在下列容器组件 [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)以及[WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md) 内使用的时候,不要与[LazyForEach](./arkts-rendering-control-lazyforeach.md) 混用。 以List为例,同时包含ForEach、LazyForEach的情形是不推荐的。
511e41f4b71Sopenharmony_ci
512e41f4b71Sopenharmony_ci## 不推荐案例
513e41f4b71Sopenharmony_ci
514e41f4b71Sopenharmony_ci开发者在使用ForEach的过程中,若对于键值生成规则的理解不够充分,可能会出现错误的使用方式。错误使用一方面会导致功能层面问题,例如[渲染结果非预期](#渲染结果非预期),另一方面会导致性能层面问题,例如[渲染性能降低](#渲染性能降低)。
515e41f4b71Sopenharmony_ci
516e41f4b71Sopenharmony_ci### 渲染结果非预期
517e41f4b71Sopenharmony_ci
518e41f4b71Sopenharmony_ci在本示例中,通过设置`ForEach`的第三个参数`KeyGenerator`函数,自定义键值生成规则为数据源的索引`index`的字符串类型值。当点击父组件`Parent`中“在第1项后插入新项”文本组件后,界面会出现非预期的结果。
519e41f4b71Sopenharmony_ci
520e41f4b71Sopenharmony_ci```ts
521e41f4b71Sopenharmony_ci@Entry
522e41f4b71Sopenharmony_ci@Component
523e41f4b71Sopenharmony_cistruct Parent {
524e41f4b71Sopenharmony_ci  @State simpleList: Array<string> = ['one', 'two', 'three'];
525e41f4b71Sopenharmony_ci
526e41f4b71Sopenharmony_ci  build() {
527e41f4b71Sopenharmony_ci    Column() {
528e41f4b71Sopenharmony_ci      Button() {
529e41f4b71Sopenharmony_ci        Text('在第1项后插入新项').fontSize(30)
530e41f4b71Sopenharmony_ci      }
531e41f4b71Sopenharmony_ci      .onClick(() => {
532e41f4b71Sopenharmony_ci        this.simpleList.splice(1, 0, 'new item');
533e41f4b71Sopenharmony_ci      })
534e41f4b71Sopenharmony_ci
535e41f4b71Sopenharmony_ci      ForEach(this.simpleList, (item: string) => {
536e41f4b71Sopenharmony_ci        ChildItem({ item: item })
537e41f4b71Sopenharmony_ci      }, (item: string, index: number) => index.toString())
538e41f4b71Sopenharmony_ci    }
539e41f4b71Sopenharmony_ci    .justifyContent(FlexAlign.Center)
540e41f4b71Sopenharmony_ci    .width('100%')
541e41f4b71Sopenharmony_ci    .height('100%')
542e41f4b71Sopenharmony_ci    .backgroundColor(0xF1F3F5)
543e41f4b71Sopenharmony_ci  }
544e41f4b71Sopenharmony_ci}
545e41f4b71Sopenharmony_ci
546e41f4b71Sopenharmony_ci@Component
547e41f4b71Sopenharmony_cistruct ChildItem {
548e41f4b71Sopenharmony_ci  @Prop item: string;
549e41f4b71Sopenharmony_ci
550e41f4b71Sopenharmony_ci  build() {
551e41f4b71Sopenharmony_ci    Text(this.item)
552e41f4b71Sopenharmony_ci      .fontSize(30)
553e41f4b71Sopenharmony_ci  }
554e41f4b71Sopenharmony_ci}
555e41f4b71Sopenharmony_ci```
556e41f4b71Sopenharmony_ci
557e41f4b71Sopenharmony_ci上述代码的初始渲染效果和点击“在第1项后插入新项”文本组件后的渲染效果如下图所示。
558e41f4b71Sopenharmony_ci
559e41f4b71Sopenharmony_ci**图9**  渲染结果非预期运行效果图  
560e41f4b71Sopenharmony_ci![ForEach-UnexpectedRenderingResult](figures/ForEach-UnexpectedRenderingResult.gif)
561e41f4b71Sopenharmony_ci
562e41f4b71Sopenharmony_ci`ForEach`在首次渲染时,创建的键值依次为"0"、"1"、"2"。
563e41f4b71Sopenharmony_ci
564e41f4b71Sopenharmony_ci插入新项后,数据源`simpleList`变为`['one', 'new item', 'two', 'three']`,框架监听到`@State`装饰的数据源长度变化触发`ForEach`重新渲染。
565e41f4b71Sopenharmony_ci
566e41f4b71Sopenharmony_ci`ForEach`依次遍历新数据源,遍历数据项"one"时生成键值"0",存在相同键值,因此不创建新组件。继续遍历数据项"new item"时生成键值"1",存在相同键值,因此不创建新组件。继续遍历数据项"two"生成键值"2",存在相同键值,因此不创建新组件。最后遍历数据项"three"时生成键值"3",不存在相同键值,创建内容为"three"的新组件并渲染。
567e41f4b71Sopenharmony_ci
568e41f4b71Sopenharmony_ci从以上可以看出,当最终键值生成规则包含`index`时,期望的界面渲染结果为`['one', 'new item', 'two', 'three']`,而实际的渲染结果为`['one', 'two', 'three', 'three']`,渲染结果不符合开发者预期。因此,开发者在使用`ForEach`时应尽量避免最终键值生成规则中包含`index`。
569e41f4b71Sopenharmony_ci
570e41f4b71Sopenharmony_ci### 渲染性能降低
571e41f4b71Sopenharmony_ci
572e41f4b71Sopenharmony_ci在本示例中,`ForEach`的第三个参数`KeyGenerator`函数处于缺省状态。根据上述[键值生成规则](#键值生成规则),此例使用框架默认的键值生成规则,即最终键值为字符串`index + '__' + JSON.stringify(item)`。当点击“在第1项后插入新项”文本组件后,`ForEach`将需要为第2个数组项以及其后的所有项重新创建组件。
573e41f4b71Sopenharmony_ci
574e41f4b71Sopenharmony_ci```ts
575e41f4b71Sopenharmony_ci@Entry
576e41f4b71Sopenharmony_ci@Component
577e41f4b71Sopenharmony_cistruct Parent {
578e41f4b71Sopenharmony_ci  @State simpleList: Array<string> = ['one', 'two', 'three'];
579e41f4b71Sopenharmony_ci
580e41f4b71Sopenharmony_ci  build() {
581e41f4b71Sopenharmony_ci    Column() {
582e41f4b71Sopenharmony_ci      Button() {
583e41f4b71Sopenharmony_ci        Text('在第1项后插入新项').fontSize(30)
584e41f4b71Sopenharmony_ci      }
585e41f4b71Sopenharmony_ci      .onClick(() => {
586e41f4b71Sopenharmony_ci        this.simpleList.splice(1, 0, 'new item');
587e41f4b71Sopenharmony_ci        console.log(`[onClick]: simpleList is ${JSON.stringify(this.simpleList)}`);
588e41f4b71Sopenharmony_ci      })
589e41f4b71Sopenharmony_ci
590e41f4b71Sopenharmony_ci      ForEach(this.simpleList, (item: string) => {
591e41f4b71Sopenharmony_ci        ChildItem({ item: item })
592e41f4b71Sopenharmony_ci      })
593e41f4b71Sopenharmony_ci    }
594e41f4b71Sopenharmony_ci    .justifyContent(FlexAlign.Center)
595e41f4b71Sopenharmony_ci    .width('100%')
596e41f4b71Sopenharmony_ci    .height('100%')
597e41f4b71Sopenharmony_ci    .backgroundColor(0xF1F3F5)
598e41f4b71Sopenharmony_ci  }
599e41f4b71Sopenharmony_ci}
600e41f4b71Sopenharmony_ci
601e41f4b71Sopenharmony_ci@Component
602e41f4b71Sopenharmony_cistruct ChildItem {
603e41f4b71Sopenharmony_ci  @Prop item: string;
604e41f4b71Sopenharmony_ci
605e41f4b71Sopenharmony_ci  aboutToAppear() {
606e41f4b71Sopenharmony_ci    console.log(`[aboutToAppear]: item is ${this.item}`);
607e41f4b71Sopenharmony_ci  }
608e41f4b71Sopenharmony_ci
609e41f4b71Sopenharmony_ci  build() {
610e41f4b71Sopenharmony_ci    Text(this.item)
611e41f4b71Sopenharmony_ci      .fontSize(50)
612e41f4b71Sopenharmony_ci  }
613e41f4b71Sopenharmony_ci}
614e41f4b71Sopenharmony_ci```
615e41f4b71Sopenharmony_ci
616e41f4b71Sopenharmony_ci以上代码的初始渲染效果和点击"在第1项后插入新项"文本组件后的渲染效果如下图所示。
617e41f4b71Sopenharmony_ci
618e41f4b71Sopenharmony_ci**图10**  渲染性能降低案例运行效果图  
619e41f4b71Sopenharmony_ci![ForEach-RenderPerformanceDecrease](figures/ForEach-RenderPerformanceDecrease.gif)
620e41f4b71Sopenharmony_ci
621e41f4b71Sopenharmony_ci点击“在第1项后插入新项”文本组件后,IDE的日志打印结果如下所示。
622e41f4b71Sopenharmony_ci
623e41f4b71Sopenharmony_ci**图11**  渲染性能降低案例日志打印图  
624e41f4b71Sopenharmony_ci![ForEach-RenderPerformanceDecreaseLogs](figures/ForEach-RenderPerformanceDecreaseLogs.png)
625e41f4b71Sopenharmony_ci
626e41f4b71Sopenharmony_ci插入新项后,`ForEach`为`new item`、 `two`、 `three`三个数组项创建了对应的组件`ChildItem`,并执行了组件的[`aboutToAppear()`](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#abouttoappear)生命周期函数。这是因为:
627e41f4b71Sopenharmony_ci
628e41f4b71Sopenharmony_ci1. 在`ForEach`首次渲染时,创建的键值依次为`0__one`、`1__two`、`2__three`。
629e41f4b71Sopenharmony_ci2. 插入新项后,数据源`simpleList`变为`['one', 'new item', 'two', 'three']`,ArkUI框架监听到`@State`装饰的数据源长度变化触发`ForEach`重新渲染。
630e41f4b71Sopenharmony_ci3. `ForEach`依次遍历新数据源,遍历数据项`one`时生成键值`0__one`,键值已存在,因此不创建新组件。继续遍历数据项`new item`时生成键值`1__new item`,不存在相同键值,创建内容为`new item`的新组件并渲染。继续遍历数据项`two`生成键值`2__two`,不存在相同键值,创建内容为`two`的新组件并渲染。最后遍历数据项`three`时生成键值`3__three`,不存在相同键值,创建内容为`three`的新组件并渲染。
631e41f4b71Sopenharmony_ci
632e41f4b71Sopenharmony_ci尽管此示例中界面渲染的结果符合预期,但每次插入一条新数组项时,`ForEach`都会为从该数组项起后面的所有数组项全部重新创建组件。当数据源数据量较大或组件结构复杂时,由于组件无法得到复用,将导致性能体验不佳。因此,除非必要,否则不推荐将第三个参数`KeyGenerator`函数处于缺省状态,以及在键值生成规则中包含数据项索引`index`。
633