当前位置: 首页 > 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.lryc.cn/news/603229.html

相关文章:

  • CNN卷积神经网络之模型评估指标(二)
  • 嵌入式系统分层开发:架构模式与工程实践(一)
  • HammerDB:一款免费开源的数据库基准测试工具
  • 【学习笔记】Lean4 定理证明 ing
  • C++ 模板类型 <T>,对函数参数传递兼容性检查
  • [MySQL] MySQL 版本不支持 ST_Distance_Sphere替代方案和解决方案
  • 数据结构【红黑树】
  • Charles中文版使用指南:如何利用抓包工具优化API调试与网络性能
  • Redis+JWT 认证管理最佳实践
  • TOPSIS(Technique for Order Preference by Similarity to Ideal Solution )简介与简单示例
  • Ext JS极速项目之 Coworkee
  • 随缘玩 一: 代理模式
  • 算法第29天|动态规划dp2:不同路径、不同路径Ⅱ、整数拆分、不同的二叉搜索树
  • 【图像处理基石】如何对遥感图像进行实例分割?
  • 小白学OpenCV系列1-图像处理基本操作
  • 在 Web3 时代通过自我主权合规重塑 KYC/AML
  • [SWPU2019]Web1
  • 链表反转中最常用的方法————三指针法
  • PHP云原生架构:容器化、Kubernetes与Serverless实践
  • redis【1】
  • 小程序视频播放,与父视图一致等样式设置
  • zama test
  • 百元级工业级核心板:明远智睿×瑞萨V2H,开启AIoT开发新纪元
  • PDF转Word免费工具!批量处理PDF压缩,合并, OCR识别, 去水印, 签名等全功能详解
  • 数据结构之时间复杂度
  • 前端css 的固定布局,流式布局,弹性布局,自适应布局,响应式布局
  • ZKmall开源商城中台架构实践:API 网关与服务治理如何撑起电商技术骨架
  • 在 PolkaVM 上用 Rust 实现 ERC20 合约的全流程开发指南
  • 接口自动化测试pytest框架
  • c++-list