在鸿蒙Next开发中,LazyForEach提供了高效的数据懒加载机制,适用于处理大量数据的列表展示等场景,可有效提升性能和内存管理。以下是其详细用法总结。
一、使用限制
- 容器组件要求:必须在特定容器组件(List、Grid、Swiper、WaterFlow)内使用,且这些组件支持配置cachedCount属性实现按需加载。
- 数量限制:容器组件内只能包含一个LazyForEach。
- 子组件规则:每次迭代必须且只能创建一个子组件,且生成的子组件必须符合父容器组件对子组件的要求。
- 条件渲染支持:允许包含在if/else条件渲染语句中,也可在其内部出现if/else条件渲染语句。
- 键值唯一性:键值生成器必须为每个数据生成唯一值,否则会导致UI组件渲染问题。
- 更新方式限制:必须使用DataChangeListener对象更新,对第一个参数dataSource重新赋值会异常,且dataSource使用状态变量时,状态变量改变不会触发UI刷新。为高性能渲染,需通过onDataChange方法更新UI并生成不同键值触发组件刷新。
- 装饰器要求:必须和@Reusable装饰器一起使用才能触发节点复用,需将@Reusable装饰在LazyForEach列表的组件上。
二、键值生成规则
- 系统默认规则:若开发者未定义keyGenerator函数,ArkUI框架使用默认函数
(item: Object, index: number) => { return viewId + '-' + index.toString(); }
(viewId在编译器转换过程中生成,同一个LazyForEach组件内其viewId一致)。 - 自定义规则:开发者可通过提供keyGenerator函数来自定义键值生成逻辑。
三、组件创建规则
1. 首次渲染
- 生成不同键值:根据键值生成规则为数据源每个数组项生成唯一键值并创建组件。
- 示例:
class BasicDataSource implements IDataSource {
// 省略部分代码...
}
class MyDataSource extends BasicDataSource {
// 省略部分代码...
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(`Hello ${i}`)
}
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Row() {
Text(item).fontSize(50)
.onAppear(() => {
console.info("appear:" + item)
})
}.margin({ left: 10, right: 10 })
}
}, (item: string) => item)
}.cachedCount(5)
}
}
-
上述代码中,键值生成规则为
item
,为数据源数组项依次生成键值Hello 0
、Hello 1
等,并创建对应的ListItem
子组件渲染到界面。 -
键值相同时错误渲染:不同数据项生成相同键值时,框架行为不可预测。例如,当所有数据项键值相同时,滑动过程中可能因框架取用缓存错误导致子组件渲染问题。
2. 非首次渲染
-
当数据源变化时,开发者需根据变化情况调用listener对应的接口通知LazyForEach更新,包括添加、删除、交换数据和改变单个数据等操作。
-
添加数据:
class BasicDataSource implements IDataSource {
// 省略部分代码...
}
class MyDataSource extends BasicDataSource {
// 省略部分代码...
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(`Hello ${i}`)
}
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Row() {
Text(item).fontSize(50)
.onAppear(() => {
console.info("appear:" + item)
})
}.margin({ left: 10, right: 10 })
}
.onClick(() => {
// 点击追加子组件
this.data.pushData(`Hello ${this.data.totalCount()}`);
})
}, (item: string) => item)
}.cachedCount(5)
}
}
-
点击子组件时,调用数据源的
pushData
方法添加数据并通知LazyForEach,LazyForEach会在相应索引处新建子组件。 -
删除数据:
class BasicDataSource implements IDataSource {
// 省略部分代码...
}
class MyDataSource extends BasicDataSource {
// 省略部分代码...
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(`Hello ${i}`)
}
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: string, index: number) => {
ListItem() {
Row() {
Text(item).fontSize(50)
.onAppear(() => {
console.info("appear:" + item)
})
}.margin({ left: 10, right: 10 })
}
.onClick(() => {
// 点击删除子组件
this.data.deleteData(this.data.dataArray.indexOf(item));
})
}, (item: string) => item)
}.cachedCount(5)
}
}
-
点击子组件时,调用数据源的
deleteData
方法删除数据并通知LazyForEach,LazyForEach会在相应索引处删除子组件。 -
交换数据:
class BasicDataSource implements IDataSource {
// 省略部分代码...
}
class MyDataSource extends BasicDataSource {
// 省略部分代码...
}
@Entry
@Component
struct MyComponent {
private moved: number[] = [];
private data: MyDataSource = new MyDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(`Hello ${i}`)
}
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: string, index: number) => {
ListItem() {
Row() {
Text(item).fontSize(50)
.onAppear(() => {
console.info("appear:" + item)
})
}.margin({ left: 10, right: 10 })
}
.onClick(() => {
this.moved.push(this.data.dataArray.indexOf(item));
if (this.moved.length === 2) {
// 点击交换子组件
this.data.moveData(this.moved[0], this.moved[1]);
this.moved = [];
}
})
}, (item: string) => item)
}.cachedCount(5)
}
}
-
首次点击子组件记录索引,再次点击时调用数据源的
moveData
方法移动数据并通知LazyForEach,LazyForEach会交换相应索引处的子组件位置。 -
改变单个数据(示例代码未完整给出改变单个数据的通知逻辑,假设已有相应方法):类似其他操作,需在数据源中修改数据并通过合适的listener方法通知LazyForEach,以触发对应子组件的更新。
四、常见使用问题
1. 渲染结果非预期
- 如键值生成规则不合理或数据源操作与通知不匹配,可能导致渲染结果不符合预期。
2. 重渲染时图片闪烁
- 可能由于组件更新过程中图片加载或处理不当导致,需优化图片相关操作或检查组件更新逻辑。
3. @ObjectLink属性变化UI未更新
- 可能是未正确使用相关装饰器或数据绑定机制,需确保@ObjectLink等装饰器的正确使用及数据的正确传递与更新。
4. 在List内使用屏幕闪烁
- 可能与List组件的配置、LazyForEach的更新机制或其他因素有关,可检查List组件属性设置、数据更新频率及设备性能等方面。
使用LazyForEach时,需严格遵循其使用限制,合理定义键值生成规则,正确处理数据源变化通知,同时注意常见问题的排查与解决,以充分发挥其数据懒加载优势,提升应用性能与用户体验。