ForEach接口基于数组类型数据来进行循环渲染,需要与容器组件配合使用,且接口返回的组件应当是允许包含在ForEach父容器组件中的子组件。例如,ListItem组件要求ForEach的父容器组件必须为 List组件 。
说明:
从API version 9开始,该接口支持在ArkTS卡片中使用。
接口描述
ForEach(
arr: Array,
itemGenerator: (item: any, index: number) => void,
keyGenerator?: (item: any, index: number) => string
)
以下是参数的详细说明:
参数名 | 参数类型 | 是否必填 | 参数描述 |
---|---|---|---|
arr | Array | 是 | 数据源,为Array 类型的数组。说明: - 可以设置为空数组,此时不会创建子组件。 - 可以设置返回值为数组类型的函数,例如 arr.slice(1, 3) ,但设置的函数不应改变包括数组本身在内的任何状态变量,例如不应使用Array.splice() ,Array.sort() 或Array.reverse() 这些会改变原数组的函数。 |
itemGenerator | (item: any, index: number) => void |
是 | 组件生成函数。 - 为数组中的每个元素创建对应的组件。 - item 参数:arr 数组中的数据项。- index 参数(可选):arr 数组中的数据项索引。说明: - 组件的类型必须是 ForEach 的父容器所允许的。例如,ListItem 组件要求ForEach 的父容器组件必须为List 组件。 |
keyGenerator | (item: any, index: number) => string |
否 | 键值生成函数。 - 为数据源 arr 的每个数组项生成唯一且持久的键值。函数返回值为开发者自定义的键值生成规则。- item 参数:arr 数组中的数据项。- index 参数(可选):arr 数组中的数据项索引。说明: - 如果函数缺省,框架默认的键值生成函数为 (item: T, index: number) => { return index + '__' + JSON.stringify(item); } - 键值生成函数不应改变任何组件状态。 |
说明:
ForEach
的itemGenerator
函数可以包含if/else
条件渲染逻辑。另外,也可以在if/else
条件渲染语句中使用ForEach
组件。- 在初始化渲染时,
ForEach
会加载数据源的所有数据,并为每个数据项创建对应的组件,然后将其挂载到渲染树上。如果数据源非常大或有特定的性能需求,建议使用LazyForEach
组件。
键值生成规则
在ForEach
循环渲染过程中,系统会为每个数组元素生成一个唯一且持久的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。
ForEach
提供了一个名为keyGenerator
的参数,这是一个函数,开发者可以通过它自定义键值的生成规则。如果开发者没有定义keyGenerator
函数,则ArkUI框架会使用默认的键值生成函数,即(item: any, index: number) => { return index + '__' + JSON.stringify(item); }
。
ArkUI框架对于ForEach
的键值生成有一套特定的判断规则,这主要与itemGenerator
函数的第二个参数index
以及keyGenerator
函数的第二个参数index
有关,具体的键值生成规则判断逻辑如下图所示。
图1 ForEach键值生成规则
说明:
ArkUI框架会对重复的键值发出警告。在UI更新的场景下,如果出现重复的键值,框架可能无法正常工作。
组件创建规则
在确定键值生成规则后,ForEach的第二个参数itemGenerator
函数会根据键值生成规则为数据源的每个数组项创建组件。组件的创建包括两种情况: ForEach首次渲染 和 ForEach非首次渲染 。
首次渲染
在ForEach首次渲染时,会根据前述键值生成规则为数据源的每个数组项生成唯一键值,并创建相应的组件。
@Entry
@Component
struct Parent {
@State simpleList: Array<string> = ['one', 'two', 'three'];
build() {
Row() {
Column() {
ForEach(this.simpleList, (item: string) => {
ChildItem({
item: item })
}, (item: string) => item)
}
.width('100%')
.height('100%')
}
.height('100%')
.backgroundColor(0xF1F3F5)
}
}
@Component
struct ChildItem {
@Prop item: string;
build() {
Text(this.item)
.fontSize(50)
}
}
运行效果如下图所示。
图2 ForEach数据源不存在相同值案例首次渲染运行效果图
在上述代码中,键值生成规则是keyGenerator
函数的返回值item
。在ForEach渲染循环时,为数据源数组项依次生成键值one
、two
和three
,并创建对应的ChildItem
组件渲染到界面上。
当不同数组项按照键值生成规则生成的键值相同时,框架的行为是未定义的。例如,在以下代码中,ForEach渲染相同的数据项two
时,只创建了一个ChildItem
组件,而没有创建多个具有相同键值的组件。
@Entry
@Component
struct Parent {
@State simpleList: Array<string> = ['one', 'two', 'two', 'three'];
build() {
Row() {
Column() {
ForEach(this.simpleList, (item: string) => {
ChildItem({
item: item })
}, (item: string) => item)
}
.width('100%')
.height('100%')
}
.height('100%')
.backgroundColor(0xF1F3F5)
}
}
@Component
struct ChildItem {
@Prop item: string;
build() {
Text(this.item)
.fontSize(50)
}
}
运行效果如下图所示。
图3 ForEach数据源存在相同值案例首次渲染运行效果图
在该示例中,最终键值生成规则为item
。当ForEach遍历数据源simpleList
,遍历到索引为1的two
时,按照最终键值生成规则生成键值为two
的组件并进行标记。当遍历到索引为2的two
时,按照最终键值生成规则当前项的键值也为two
,此时不再创建新的组件。
非首次渲染
在ForEach组件进行非首次渲染时,它会检查新生成的键值是否在上次渲染中已经存在。如果键值不存在,则会创建一个新的组件;如果键值存在,则不会创建新的组件,而是直接渲染该键值所对应的组件。例如,在以下的代码示例中,通过点击事件修改了数组的第三项值为"new three",这将触发ForEach组件进行非首次渲染。
@Entry
@Component
struct Parent {
@State simpleList: Array<string> = ['one', 'two', 'three'];
build() {
Row() {
Column() {
Text('点击修改第3个数组项的值')
.fontSize(24)
.fontColor(Color.Red)
.onClick(() => {
this.simpleList[2] = 'new three';
})
ForEach(this.simpleList, (item: string)