一、前言
ForEach接口基于数组类型数据来进行循环渲染,需要与容器组件配合使用,且接口返回的组件应当是允许包含在ForEach父容器组件中的子组件。例如,ListItem组件要求ForEach的父容器组件必须为List组件。
API参数说明见:ForEach API参数说明。
说明
从API version 9开始,该接口支持在ArkTS卡片中使用。
二、键值生成规则
在ForEach循环渲染过程中,系统会为每个数组元素生成一个唯一且持久的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。
ForEach提供了一个名为keyGenerator的参数,这是一个函数,开发者可以通过它自定义键值的生成规则。如果开发者没有定义keyGenerator函数,则ArkUI框架会使用默认的键值生成函数,即(item: Object, index: number) => { return index + ‘__’ + JSON.stringify(item); }。
ArkUI框架对于ForEach的键值生成有一套特定的判断规则,这主要与itemGenerator函数的第二个参数index以及keyGenerator函数的第二个参数index有关,具体的键值生成规则判断逻辑如下图所示。
ForEach键值生成规则
说明
ArkUI框架会对重复的键值发出警告。在UI更新的场景下,如果出现重复的键值,框架可能无法正常工作,具体请参见渲染结果非预期。
三、组件创建规则
在确定键值生成规则后,ForEach的第二个参数itemGenerator函数会根据键值生成规则为数据源的每个数组项创建组件。组件的创建包括两种情况:ForEach首次渲染和ForEach非首次渲染。
3.1 首次渲染
在ForEach首次渲染时,会根据前述键值生成规则为数据源的每个数组项生成唯一键值,并创建相应的组件。
@Component
struct ChildItem {
@Prop item: string
build() {
Text(this.item)
.fontSize(20)
.fontWeight(FontWeight.Medium)
.fontColor($r('app.color.c_black'))
.backgroundColor("#f5f5f5")
.borderRadius(8)
.padding(16)
.width('100%')
}
}
@Entry
@Component
struct TestForEach1 {
@State message: string = 'ForEach:循环渲染';
@State simpleList: Array<string> = ['one', 'two', 'three'];
build() {
Column({ space: 10 }) {
ForEach(this.simpleList, (item: string) => {
ChildItem({ item: item })
}, (item: string) => item)
}
.height('100%')
.width('100%')
.padding(16)
.backgroundColor("#ffffff")
}
}
ForEach数据源不存在相同值案例首次渲染运行效果图
在上述代码中,键值生成规则是keyGenerator函数的返回值item。在ForEach渲染循环时,为数据源数组项依次生成键值one、two和three,并创建对应的ChildItem组件渲染到界面上。
当不同数组项按照键值生成规则生成的键值相同时,框架的行为是未定义的。例如,在以下代码中,ForEach渲染相同的数据项two时,只创建了一个ChildItem组件,而没有创建多个具有相同键值的组件。
@Component
struct ChildItem {
@Prop item: string
build() {
Text(this.item)
.fontSize(20)
.fontWeight(FontWeight.Medium)
.fontColor($r('app.color.c_black'))
.backgroundColor("#f5f5f5")
.borderRadius(8)
.padding(16)
.width('100%')
}
}
@Entry
@Component
struct TestForEach1 {
@State message: string = 'ForEach:循环渲染';
// @State simpleList: Array<string> = ['one', 'two', 'three'];
@State simpleList: Array<string> = ['one', 'two', 'two', 'three'];
build() {
Column({ space: 10 }) {
ForEach(this.simpleList, (item: string) => {
ChildItem({ item: item })
}, (item: string) => item)
}
.height('100%')
.width('100%')
.padding(16)
.backgroundColor("#ffffff")
}
}
ForEach数据源存在相同值案例首次渲染运行效果图
在该示例中,最终键值生成规则为item。当ForEach遍历数据源simpleList,遍历到索引为1的two时,按照最终键值生成规则生成键值为two的组件并进行标记。当遍历到索引为2的two时,按照最终键值生成规则当前项的键值也为two,此时不再创建新的组件。
3.2 非首次渲染
在ForEach组件进行非首次渲染时,它会检查新生成的键值是否在上次渲染中已经存在。如果键值不存在,则会创建一个新的组件;如果键值存在,则不会创建新的组件,而是直接渲染该键值所对应的组件。例如,在以下的代码示例中,通过点击事件修改了数组的第三项值为"new three",这将触发ForEach组件进行非首次渲染。
@Component
struct Child