当前位置: 首页 > news >正文

ArkTS懒加载LazyForEach的基本使用

在 ArkTS 的开发中,如果你要渲染一个很长的列表,比如商品列表、评论列表或者朋友圈动态,用传统的循环结构(比如 ForEach)很容易导致性能问题,尤其是加载慢、卡顿甚至内存暴涨。

这时候就要用到 懒加载渲染组件——LazyForEach

LazyForEach 是 ArkTS 提供的一种 延迟渲染列表项的方式。只有当列表项真正要被显示在屏幕上时,相关组件才会被创建和渲染,从而节省内存和提升性能。

可以把它理解成 ArkTS 中的“虚拟滚动列表”。

LazyForEach的使用限制

  • LazyForEach必须在容器组件内使用仅有List、Grid、Swiper以及WaterFlow组件支持数据懒加载(可配置cachedCount属性,即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。支持数据懒加载的父组件根据自身及子组件的高度或宽度计算可视区域内需布局的子节点数量,高度或宽度的缺失会导致部分场景懒加载失效

catchdCount

List设置cachedCount后,显示区域外上下各会预加载并布局cachedCount行ListItem。

  • LazyForEach依赖生成的键值判断是否刷新子组件,键值不变则不触发刷新。
  • 容器组件内只能包含一个LazyForEach。以List为例,不推荐同时包含ListItem、ForEach、LazyForEach。也不推荐同时包含多个LazyForEach。
  • LazyForEach在每次迭代中,必须创建且只允许创建一个子组件;即LazyForEach的子组件生成函数有且只有一个根组件
  • 生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。
  • 允许LazyForEach包含在if/else条件渲染语句中,也允许LazyForEach中出现if/else条件渲染语句。
  • 键值生成器必须针对每个数据生成唯一的值,如果键值相同,将导致键值相同的UI组件渲染出现问题。
  • LazyForEach必须使用DataChangeListener对象进行更新。重新赋值第一个参数dataSource会导致异常;dataSource使用状态变量时,状态变量改变不会触发LazyForEach的UI刷新
  • 为了高性能渲染,使用DataChangeListener对象的onDataChange方法更新UI时,需要生成不同于原来的键值来触发组件刷新。

LazyForEach键值生成规则

LazyForEach提供了参数keyGenerator,开发者可以使用该函数生成自定义键值。如果未定义keyGenerator函数,ArkUI框架将使用默认的键值生成函数:(item: Object, index: number) => { return viewId + ‘-’ + index.toString(); }。viewId在编译器转换过程中生成,同一个LazyForEach组件内的viewId一致。

基本语法

LazyForEach(dataSource: IDataSource,             // 需要进行数据迭代的数据源itemGenerator: (item: any, index?: number) => void,  // 子组件生成函数keyGenerator?: (item: any, index?: number) => string // 键值生成函数
): void
  • dataSource:IDataSource

    LazyForEach数据源,需要开发者实现相关接口

  • itemGenerator:(item: any, index: number) => void

    子组件生成函数,为数组中的每一个数据项创建一个子组件。

  • keyGenerator: (item: any, index: number) => string

    键值生成函数,用于给数据源中的每一个数据项生成唯一且固定的键值。修改数据源中的一个数据项若不影响其生成的键值,则对应组件不会被更新,否则此处组件就会被重建更新。keyGenerator参数是可选的,但是,为了使开发框架能够更好地识别数组更改并正确更新组件,建议提供

实现IDataSource接口

interface IDataSource {totalCount(): number; // 获得数据总数getData(index: number): Object; // 获取索引值对应的数据registerDataChangeListener(listener: DataChangeListener): void; // 注册数据改变的监听器unregisterDataChangeListener(listener: DataChangeListener): void; // 注销数据改变的监听器
}

DataChangeListener接口

interface DataChangeListener {onDataReloaded(): void; // 重新加载数据完成后调用onDataAdded(index: number): void; // 添加数据完成后调用onDataMoved(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换完成后调用onDataDeleted(index: number): void; // 删除数据完成后调用onDataChanged(index: number): void; // 改变数据完成后调用onDataAdd(index: number): void; // 添加数据完成后调用onDataMove(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换完成后调用onDataDelete(index: number): void; // 删除数据完成后调用onDataChange(index: number): void; // 改变数据完成后调用
}

创建LazyForEach

首次渲染

在LazyForEach首次渲染时,会根据上述键值生成规则为数据源的每个数组项生成唯一键值并创建相应的组件。

局部代码如下:

class MyDataSource extends BasicDataSource {private dataArray: string[] = [];public totalCount(): number {return this.dataArray.length;}public getData(index: number): string {return this.dataArray[index];}public pushData(data: string): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}
}@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) // 设置显示区域外上下预加载布局}
}

渲染结果如图

在这里插入图片描述

非首次渲染

当LazyForEach数据源发生变化,需要再次渲染时,开发者应根据数据源的变化情况调用listener对应的接口,通知LazyForEach做相应的更新。

局部代码如下:

class MyDataSource extends BasicDataSource {private dataArray: string[] = [];public totalCount(): number {return this.dataArray.length;}public getData(index: number): string {return this.dataArray[index];}public pushData(data: string): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}
}@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)}
}

渲染结果如图:
在这里插入图片描述

LazyForEach之前:先实现IDataSource接口

// 实现IDataSource接口,用于管理listener监听,以及通知LazyForEach数据更新
class BasicDataSource implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: string[] = [];public totalCount(): number {return this.originDataArray.length;}public getData(index: number): string {return this.originDataArray[index];}// 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {console.info('add listener');this.listeners.push(listener);}}// 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {console.info('remove listener');this.listeners.splice(pos, 1);}}// 通知LazyForEach组件需要重载所有子组件notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();});}// 通知LazyForEach组件需要在index对应索引处添加子组件notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);// 写法2:listener.onDatasetChange([{type: DataOperationType.ADD, index: index}]);});}// 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件notifyDataChange(index: number): void {this.listeners.forEach(listener => {listener.onDataChange(index);// 写法2:listener.onDatasetChange([{type: DataOperationType.CHANGE, index: index}]);});}// 通知LazyForEach组件需要在index对应索引处删除该子组件notifyDataDelete(index: number): void {this.listeners.forEach(listener => {listener.onDataDelete(index);// 写法2:listener.onDatasetChange([{type: DataOperationType.DELETE, index: index}]);});}// 通知LazyForEach组件将from索引和to索引处的子组件进行交换notifyDataMove(from: number, to: number): void {this.listeners.forEach(listener => {listener.onDataMove(from, to);// 写法2:listener.onDatasetChange(//         [{type: DataOperationType.EXCHANGE, index: {start: from, end: to}}]);});}notifyDatasetChange(operations: DataOperation[]): void {this.listeners.forEach(listener => {listener.onDatasetChange(operations);});}
}
http://www.dtcms.com/a/303884.html

相关文章:

  • 【Delphi】快速理解泛型(Generics)
  • 疯狂星期四文案网第23天运营日记
  • 第2章 cmd命令基础:常用基础命令(1)
  • 为什么分类任务偏爱交叉熵?MSE 为何折戟?
  • Aspose:构建高效文档处理系统的专业组件选择
  • 无人机数传链路模块技术分析
  • 31.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--财务服务--收支分类
  • Oracle 和 MySQL 中的日期类型比较
  • DeepSeek MoE 技术解析:模型架构、通信优化与负载均衡
  • 四、Linux核心工具:Vim, 文件链接与SSH
  • 暑期算法训练.10
  • 如何选择AI IDE?对比Cursor分析功能差异
  • 【Zabbix】Ansible批量部署ZabbixAgent
  • 三步给小智ESP32S3智能语音硬件接入小程序打通MCP服务
  • X-Forwarded-For解析
  • 海外短剧系统架构设计:从0到1搭建高并发微服务平台
  • 基础算法的系统性总结
  • 分布式微服务--RPC:原理、使用方式、与 HTTP/REST 的区别与选择
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-43,(知识点:晶体管、复合管、达林顿管)
  • 【iOS】类扩展与关联对象
  • 时序数据库选型指南:为什么IoTDB正在重新定义工业大数据规则?
  • 谷歌采用 Ligero 构建其 ZK 技术栈
  • QML 3D曲面图(Surface3D)技术
  • p5.js 从零开始创建 3D 模型,createModel入门指南
  • Adv. Sci. 前沿:非零高斯曲率3D结构可逆转换!液晶弹性体多级形变新策略
  • VSCode使用Code Runner运行C/C++输出[Done] exited with code=0 in xxx seconds
  • Marin说PCB之POC电路layout设计仿真案例---10
  • 机械学习--线性回归---三个小案例
  • p5.js 矩形rect绘制教程
  • Ubuntu环境下搭建CUDA编程环境