ForEach刷新UI机制
官网地址:ForEach
在ArkUI中,提供了ForEach循环语句,用来初始化一个列表数据,我们知道,当ForEach中的数组发生变化时,会引起UI的刷新,但是究竟如何变化,会引起UI怎样的刷新,还需要进一步的去探索其逻辑。
1. ForEach参数和规则
参数:
参数名 | 类型 | 必填 | 说明 |
arr | Array<Object> | 是 | 数据源,为Array类型的数组。 说明: - 可以设置为空数组,此时不会创建子组件。 - 可以设置返回值为数组类型的函数,例如arr.slice(1, 3),但设置的函数不应改变包括数组本身在内的任何状态变量,例如不应使用Array.splice(),Array.sort()或Array.reverse()这些会改变原数组的函数。 |
itemGenerator | (item: Object, index: number) => void | 是 | 组件生成函数。 - 为数组中的每个元素创建对应的组件。 - item参数:arr数组中的数据项。 - index参数(可选):arr数组中的数据项索引。 说明: - 组件的类型必须是ForEach的父容器所允许的。例如,ListItem组件要求ForEach的父容器组件必须为List组件。 |
keyGenerator | (item: Object, index: number) => string | 否 | 键值生成函数。 - 为数据源arr的每个数组项生成唯一且持久的键值。函数返回值为开发者自定义的键值生成规则。 - item参数:arr数组中的数据项。 - index参数(可选):arr数组中的数据项索引。 说明: - 如果函数缺省,框架默认的键值生成函数为(item: T, index: number) => { return index + '__' + JSON.stringify(item); } - 键值生成函数不应改变任何组件状态。 |
键值的生成规则和itemGenerator 、keyGenerator有关:
1.如果是keyGenerator这个函数缺省,此时生成规则由框架确定,生成规则为item和index拼接,(item: any, index: number)=>{ return index +“_”+ JSON.stringify(item); }。
2.如果keyGenerator没有缺省且未包含index,当itemGenerator中包含index,生成的规则是自定义键值与index拼接成的字符串,如(item)=>item+2 对应的键值是 index+'_'+(item+2),如果itemGenerator中未包含index,此时keyGenerator的生成规则是由开发者自定义的键值生成规则。
3.如果keyGenerator没有缺省,且包含index,此时不管itemGenerator中是否包含index,生成的键值规则都是开发者自定义的键值生成规格,框架不会对去拼接index。
2. 场景和示例
ForEach在初始化时,会加载数组中所有的数据,并为其创建控件,也就是厨师数组如果有n项,就会创建n个对象
@Component
export struct ForEachView {
@State private list: number[] = [1, 2, 3]
private list1: number[] = [1, 2]
private list2: number[] = [1, 2, 3, 4, 5]
private isOne: boolean = true;
build() {
Column({ space: 20 }) {
Column({ space: 10 }) {
ForEach(this.list, (item: number) => {
Child({ item: item })
}, (item: number, index: number) => {
console.debug(`ForEach: item = ${item}, index = ${index}`)
return item.toString()
})
}
Row({ space: 10 }) {
Button("add")
.onClick(() => {
this.list.push(4)
})
Button("insert")
.onClick(() => {
this.list.splice(1, 0, 5)
})
Button("delete")
.onClick(() => {
this.list.pop();
})
Button("changeArray")
.onClick(() => {
if (this.isOne) {
this.list = this.list1
} else {
this.list = this.list2
}
this.isOne = !this.isOne;
})
}
}
}
}
@Component
struct Child {
@Prop item: number;
aboutToAppear(): void {
console.debug(`aboutToAppear: child${this.item}`)
}
build() {
Text(this.item.toString())
.width(100)
.height(50)
.border({ width: 2, color: Color.Red })
}
aboutToDisappear(): void {
console.debug(`aboutToDisappear: child${this.item}`)
}
}
2.1. 场景一:初始化
运行结果:
初始化时,list中有三个元素,ForEach遍历所有元素,并创建了三个Child控件
2.2. 场景二:数组增加元素
运行结果:
可以看出,增加了Item 4后,ForEach循环还是遍历的四遍,但是此时只有child4执行了aboutToAppear的方法。这是因为ForEach的keyGenerator设置唯一的key值,ForEach刷新时,发现如果相同的key值的控件如果已经存在,就不会重新创建。
2.3. 场景三:数组插入元素
运行结果:
插入和增加一样,ForEach也会全部遍历,但是只创建了不存在的child5
2.4. 场景四:删除
运行结果:
ForEach遍历剩余的元素,并将删除的元素下树,调用child3的aboutToDisapper的方法
2.5. 场景五:更换数组对象
- 将数组list1赋值list, 元素数量减少
运行结果:
由于list1和list之间有部分项的重合,所以,list1赋值给list后,重复的部分之前已经存在,不创建,list1没有的部分则进行了删除
- 将list2赋值给list,元素数量增加
运行结果:
ForEach对所有元素遍历,对不存在的元素进行创建
- 赋值完全不同的list3
运行结果:
ForEach遍历数组所有元素,将原来的元素全部移除,然后创建新的元素。
3. 总结
对于ForEach而言,只要数组发生了变化, 无论是长度变化还是重新赋值, ForEach都回重新遍历一遍。但是ForEach会根据keyGenerator的值判断是否需要重新创建控件,所以keyGenerator是非常关键的参数,建议自己定义一个生成规则,不要用系统默认的。