本文将深入解析HarmonyOS中LazyForEach的工作原理、性能优势、实战优化技巧及常见问题解决方案,帮助你构建流畅的长列表体验。

1. LazyForEach 核心优势与原理

LazyForEach 是鸿蒙ArkUI框架中为高性能列表渲染设计的核心组件,其核心设计思想基于动态加载资源回收机制。与一次性加载全量数据的ForEach不同,LazyForEach仅渲染当前屏幕可视区域内的列表项及少量缓存项,从而大幅降低内存消耗,支持万级数据的流畅滚动。

1.1 与 ForEach 的关键差异

特性LazyForEachForEach
渲染策略按需加载 + 节点回收全量渲染
内存占用动态控制(更低)固定(更高)
适用场景长列表/复杂项短列表/简单项
性能优化自动回收 + 复用无特殊优化
数据更新需 DataChangeListener直接响应式更新

数据量超过100条时,LazyForEach的优势愈发明显。万条数据下,其内存占用可降低86%,首屏耗时减少77%,丢帧率从58.2%降至0%。

2. 基础用法与数据源实现

2.1 基础代码示例

// 1. 定义数据源,必须实现IDataSource接口
class BasicDataSource implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: string[] = [];totalCount(): number { return this.originDataArray.length; }getData(index: number): string { return this.originDataArray[index]; }registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {this.listeners.push(listener);}}unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) { this.listeners.splice(pos, 1); }}// 数据变更时通知监听器notifyDataReload(): void {this.listeners.forEach(listener => { listener.onDataReloaded(); })}notifyDataAdd(index: number): void {this.listeners.forEach(listener => { listener.onDataAdd(index); })}
}// 2. 在组件中使用LazyForEach
@Entry
@Component
struct PerformanceList {private data: BasicDataSource = new BasicDataSource();build() {List({ space: 10 }) {LazyForEach(this.data,(item: string) => {ListItem() {Text(item).fontSize(16)}},(item: string) => item // 唯一键生成器)}.cachedCount(3) // 设置缓存数量}
}

2.2 关键实现要点

  • 唯一键生成器:必须提供,用于组件复用时的身份标识,建议使用项的唯一ID而非索引。
  • 数据源接口:必须实现IDataSource接口的totalCount()getData()等方法。
  • 数据更新:严禁直接修改数据源数组,必须通过数据源中注册的监听器(DataChangeListener)通知变更。

3. 性能优化实战技巧

3.1 缓存策略(cachedCount)

通过cachedCount预加载屏幕外指定数量的列表项,可有效解决快速滑动时的白块问题。

List() {LazyForEach(this.dataSource, (item) => { /* ... */ })
}
.cachedCount(5) // 推荐值为屏幕可见项数的1-2倍

设置建议:一屏显示6条 → 设cachedCount=3(屏幕外缓存一半);若列表含图片/视频等大资源,可适当增大缓存(如cachedCount=6)。

3.2 组件复用(@Reusable)

使用@Reusable装饰器标记可复用组件,滑出可视区的组件会被存入复用池,需要时直接更新数据而非重新创建,显著降低组件创建时间和内存开销。

@Reusable
@Component
struct ReusableListItem {@Prop item: MyDataItem; // 使用@Prop而非@Link接收数据aboutToReuse(params: Record<string, Object>) {// 组件复用时更新数据,比重新创建快10倍!this.item = params.item as MyDataItem;}build() {Row() {Image(this.item.avatar).width(50).height(50)Text(this.item.name).fontSize(16)}}
}
// 在LazyForEach中使用
LazyForEach(this.data, (item: MyDataItem) => {ListItem() {ReusableListItem({ item }) // 传递参数}
}, (item) => item.id.toString())

3.3 布局优化

  • 减少嵌套层级:使用RelativeContainer实现扁平化布局,将所有组件置于同一层级,减少渲染计算量。
  • 慎用条件语句:避免在列表项中使用if/else控制不同布局结构,这会阻碍组件复用。可拆分为不同组件或用display属性控制显隐。

3.4 图片优化

对于网络图片,使用同步加载或预加载避免复用导致的闪烁。

@Reusable
@Component
struct StableImage {@Prop url: string;private cachedImage = new LRUCache(20); // 使用LRU缓存build() {Image(this.cachedImage.get(this.url) || fetchImage(this.url)).syncLoad(true) // 同步加载}
}

3.5 数据更新优化

使用@ObjectLink@Observed进行数据双向绑定,避免不必要的深拷贝和组件重建。

@Observed
class MyDataItem {id: string;name: string;
}@Reusable
@Component
struct MyListItem {@ObjectLink item: MyDataItem; // 使用@ObjectLink而非@Propbuild() {// ...}
}

4. 常见问题与解决方案

  1. 列表项错乱
    • 根因:键值生成规则不唯一,或使用了索引(index)作为键。
    • 解决:确保使用唯一且稳定的标识(如item.id),或采用复合键${id}_${timestamp}
  2. 图片闪烁
    • 根因:组件复用时Image重新加载。
    • 解决:使用Image.syncLoad(true)或实现图片缓存机制(如LRUCache)。
  3. 数据更新后UI“闪”或先展示旧数据
    • 根因:更新数据时改变了可视区及缓存区内组件的键值[key]。
    • 解决:更新数据时不要改变可视区及缓存区(cachedCount范围内)组件的键值。应通过@ObjectLink@Observed机制局部更新数据,或手动调用组件更新方法。
  4. 滑动卡顿
    • 排查: 检查cachedCount是否设置合理。 确认@ReusablereuseId是否正确使用。 避免在列表滑动过程中进行大量计算或耗时操作。
    • 优化:使用@Builder替代自定义组件@Component以减少嵌套和节点创建开销。

5. 总结

LazyForEach是处理HarmonyOS长列表的首选方案,通过按需加载缓存策略组件复用布局优化等手段,可显著提升性能。记住以下要点:

  • 键值唯一:确保键生成器返回稳定唯一的标识。
  • 合理缓存:设置cachedCount为可视项数量的1-2倍。
  • 组件复用:对复杂列表项使用@Reusable装饰器。
  • 数据更新:通过数据监听器通知变更,并使用@ObjectLink进行高效更新。
  • 布局扁平:减少嵌套层级,优先使用RelativeContainer

对于不足100项的短列表,使用ForEach更为简单;但对于长列表,LazyForEach是保障流畅体验的关键。

需要参加鸿蒙认证的请点击 鸿蒙认证链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/100305.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/100305.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/100305.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot 全栈优化:服务器、数据、缓存、日志的场景应用!

Spring Boot以其“开箱即用”闻名&#xff0c;但默认配置往往在高并发场景下成为瓶颈&#xff1a;Tomcat线程堵塞、数据库连接耗尽、缓存命中率低下、日志洪水般淹没磁盘。想象一个电商微服务&#xff0c;峰值流量下响应迟钝&#xff0c;用户流失——这不是宿命&#xff0c;而是…

Leetcode sql 50 ~5

select product_idfrom Productswhere low_fats Y and recyclable Y;SQL 规定&#xff1a;null 的比较必须用 is null 或 is not null&#xff0c;不能用普通的等号&#xff08;&#xff09;。# Write your MySQL query statement below select name from Customer where ref…

C#高并发与并行理解处理

目录 1.什么是IO密集型任务/CPU密集型任务 2.高并发概念和技术实现 2.并行&#xff08;Parallelist&#xff09;概念和技术实现 4.核心区别对比 1.什么是IO密集型任务/CPU密集型任务 1.IO密集型任务&#xff1a; 定义&#xff1a;任务核心逻辑不依赖CPU计算&#xff0c;而是…

正点原子STM32F407 U盘升级程序(IAP)OTA Bootloader APP USB升级+FATFS+USB Host

正点原子STM32F407 U盘升级程序&#xff08;IAP&#xff09;OTA Bootloader APP USB升级FATFSUSB HostChapter0 解决STM32 Bootloader跳转APP失败问题问题背景问题描述问题解决原APP跳转的函数为&#xff1a;修改APP程序main入口处Chapter1 MDK如何生成*.bin格式的文件Chapter2…

MySQL 8.0 在 Ubuntu 22.04 中如何将启用方式改为mysql_native_password(密码认证)

MySQL 8.0 在 Ubuntu 22.04 中默认启用了 auth_socket 认证方式(而非密码认证),导致 mysql_secure_installation 跳过了 root 密码设置。这会直接影响后续用 Navicat 连接 MySQL(因为 Navicat 需要密码登录),必须手动调整 root 用户的认证方式并设置密码。 核心问题:au…

七层网络协议-面试

七层网络协议概述七层网络协议&#xff0c;即OSI&#xff08;Open Systems Interconnection&#xff09;模型&#xff0c;是由国际标准化组织&#xff08;ISO&#xff09;提出的网络通信框架。它将网络通信过程划分为七个层次&#xff0c;每一层负责特定的功能&#xff0c;并通…

【Blender】二次元人物制作【二】:五官的制作

一、制作眼睛 选中眼眶内部的一圈线。shiftD复制出来调整成圆形&#xff0c;然后F快捷键填充将眼睛放在眼框内合适的位置&#xff0c;并用i键进行几次内插&#xff0c;做出瞳孔&#xff0c;并且将内部的眼瞳做得稍微向内凹陷一点。二、制作睫毛 选中眼眶上半部分的面&#xff0…

Deepin 25 系统安装 Docker:完整教程 + 常见问题解决

Deepin 25 系统安装 Docker&#xff1a;完整教程 常见问题解决 作为基于 Debian 的 Linux 发行版&#xff0c;Deepin 25 因系统目录&#xff08;如/usr&#xff09;默认只读的特性&#xff0c;安装 Docker 时需特殊处理 GPG 公钥存储路径。本文结合社区实践&#xff0c;整理出…

Redis MySQL小结

问题1&#xff1a;Redis为什么高效&#xff1f;答&#xff1a;基于内存&#xff0c;reactor&#xff0c;value的数据组织&#xff08;五种数据结构&#xff09;&#xff0c;KV的数据组织方式&#xff08;渐进hash&#xff09;问题2&#xff1a;跳表是什么&#xff1f;和红黑树的…

Flink on YARN 实战问题排查指南(精华版)

一、客户端常见问题速查 ‌1. JAR加载失败终极解法‌报错提示&#xff1a;"Could not build the program from JAR file" 核心原因&#xff1a;80%的情况是Hadoop依赖缺失 黄金配置&#xff1a;export HADOOP_CONF_DIR${HADOOP_HOME}/etc/hadoop export HADOOP_CLASS…

迅为RK3576开发板Android12制作使用系统签名

配套资料在网盘资料“iT0P-RK3576 开发板\02_【iTOP-RK3576 开发板】开发资料\ 08Android 系统开发配套资料\ 07 Android 制作使用系统签名”目录下制作签名文件 在 Android 源码 build/make/target/product/security/下存放着签名文件&#xff0c;如下所示&#xff1a;将北京迅…

django连接minio实现文件上传下载(提供接口示例)

django连接minio实现文件上传下载&#xff08;提供接口示例&#xff09;项目环境前提1.模型创建2. 在 settings.py 中添加 MINIO 配置3.创建 MINIO 工具类4.创建序列化器5. 创建视图6. 配置 URL 路由7.接口测试项目环境前提 已安装python3.8以上环境已安装djangorestframework…

Kafka消息队列进阶:发送策略与分区算法优化指南

Kafka消息队列进阶&#xff1a;发送策略与分区算法优化指南 目录Kafka消息队列进阶&#xff1a;发送策略与分区算法优化指南摘要1. Kafka消息发送模式概述1.1 消息发送的核心流程1.2 三种发送模式对比2. 同步发送模式详解2.1 同步发送实现原理2.2 同步发送性能优化3. 异步发送模…

【VScode】ssh报错

【VScode】ssh报错1. ssh报错2. 解决1. ssh报错 Failed to parse remote port from server output 2. 解决 windows电脑删除 C:\Users\username\.ssh\known_hosts linux cd /home/username/.vscode-server/ rm -rf ~/.vscode-server重新回到Vscode连接ok

Grafana+Loki+Alloy构建企业级日志平台

1.日志系统介绍日志系统&#xff1a;GLA、ELK、数仓 ⽇志处理流程&#xff1a;采集 > 存储 > 检索 > 可视化日志系统工作流程&#xff1a;日志平台的目的&#xff1a;统一聚合分散的日志日志平台搭建方案&#xff1a;ELK&#xff1a;ElasticSearch:存储日志&#xff0…

老梁聊全栈系列:(阶段一)现代全栈的「角色边界」与「能力雷达图」

JAVA Vue/React 双栈工程师的「T 型→E 型」进化指南 接上篇《从单体到云原生的演进脉络》 大家好&#xff0c;我是技术老梁&#xff0c;这是系列文章的第五篇。欢迎大家讨论&#xff0c;分享经验。如果知识对你有用&#xff0c;关注我&#xff0c;多多支持老梁&#xff0c;鼓…

使用 C# 设置 Excel 单元格格式

在实际报表开发中&#xff0c;Excel 的可读性和美观性与数据本身同样重要。合理的单元格格式设置不仅能让数据一目了然&#xff0c;还能让报表显得更专业。通过使用 C#&#xff0c;开发者可以精确控制 Excel 文件的单元格样式&#xff0c;无需依赖 Microsoft Office。 本文演示…

Redis篇章3:Redis 企业级缓存难题全解--预热、雪崩、击穿、穿透一网打尽

在企业级应用场景中&#xff0c;Redis 作为高性能缓存利器&#xff0c;极大提升了系统响应速度&#xff0c;但随着业务复杂度和并发量的攀升&#xff0c;缓存相关的各类挑战也接踵而至。比如系统启动时缓存缺失导致的数据库压力、大量缓存同时失效引发的连锁故障、热点数据过期…

【数值分析】02-绪论-误差

参考资料&#xff1a; 书籍&#xff1a; 数值分析简明教程/王兵团&#xff0c;张作泉&#xff0c;张平福编著. --北京&#xff1a;清华大学出版社&#xff1b;北京交通大学出版社&#xff0c;2012.8 视频&#xff1a;学堂在线APP中北京交通大学“数值分析I” 前期回顾 【数值分…

P3918 [国家集训队] 特技飞行

P3918 [国家集训队] 特技飞行 - 洛谷 思路&#xff1a; 因为如果连续进行相同的动作&#xff0c;乘客会感到厌倦&#xff0c;所以定义某次动作的价值为(距上次该动作的时间) ci​&#xff0c;若为第一次进行该动作&#xff0c;价值为 0。同一个动作&#xff0c;价值为ci*(最后一…