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

[鸿蒙2025领航者闯关]List组件性能优化

问题描述

在 HarmonyOS 开发中,List 列表是最常用的组件,但数据量大时容易出现性能问题:

  • 滚动卡顿,帧率下降
  • 加载 1000+ 条数据时崩溃
  • 列表项复杂时渲染慢
  • 内存占用过高

关键字:List 性能优化LazyForEachcachedCount列表复用

解决方案

1. 性能优化核心原则

虚拟列表: 只渲染可见区域 懒加载: LazyForEach按需加载 缓存复用: cachedCount缓存列表项 简化渲染: 减少组件层级

2. 完整优化方案

优化前:性能差的写法
// ❌ 性能差:使用ForEach,全量渲染 @Entry @Component struct BadListDemo { @State items: Item[] = []; // 假设有10000条数据 async aboutToAppear(): Promise<void> { // 一次性加载10000条数据 this.items = await loadAllItems(); // ❌ 内存爆炸 } build() { List() { // ❌ ForEach会渲染所有10000个列表项 ForEach(this.items, (item: Item) => { ListItem() { this.buildComplexItem(item); // ❌ 复杂组件 } }) } } @Builder buildComplexItem(item: Item) { Column() { // ❌ 嵌套层级深 Row() { Column() { Image(item.image) .width(80) .height(80); Column() { Text(item.title).fontSize(16); Text(item.desc).fontSize(14); Text(item.time).fontSize(12); } } } } .width('100%') .padding(16) .backgroundColor(Color.White) } }

问题:

  • ForEach 渲染全部数据,内存爆炸
  • 组件层级深,渲染慢
  • 没有缓存,滚动卡顿
优化后:高性能写法
// ✅ 性能优化:使用LazyForEach + 缓存 import { BasicDataSource } from './BasicDataSource'; ​ /** * 数据源实现 */ class ItemDataSource extends BasicDataSource { private items: Item[] = []; totalCount(): number { return this.items.length; } getData(index: number): Item { return this.items[index]; } addData(item: Item): void { this.items.push(item); this.notifyDataAdd(this.items.length - 1); } pushData(data: Item[]): void { this.items.push(...data); this.notifyDataReload(); } } ​ @Entry @Component struct OptimizedListDemo { private dataSource: ItemDataSource = new ItemDataSource(); @State isLoading: boolean = false; async aboutToAppear(): Promise<void> { await this.loadInitialData(); } /** * 分页加载 */ async loadInitialData(): Promise<void> { this.isLoading = true; // ✅ 只加载第一页(20条) const items = await loadItems(0, 20); this.dataSource.pushData(items); this.isLoading = false; } /** * 加载更多 */ async loadMore(): Promise<void> { if (this.isLoading) { return; } this.isLoading = true; const currentCount = this.dataSource.totalCount(); const items = await loadItems(currentCount, 20); this.dataSource.pushData(items); this.isLoading = false; } build() { List({ space: 12 }) { // ✅ 使用LazyForEach,按需渲染 LazyForEach(this.dataSource, (item: Item, index: number) => { ListItem() { this.buildOptimizedItem(item); } }, (item: Item) => item.id.toString()) // ✅ 提供唯一key } .width('100%') .height('100%') .edgeEffect(EdgeEffect.Spring) // ✅ 缓存10个列表项 .cachedCount(10) // ✅ 滚动到底部时加载更多 .onReachEnd(() => { this.loadMore(); }) } /** * 优化后的列表项 */ @Builder buildOptimizedItem(item: Item) { // ✅ 减少嵌套层级 Row({ space: 12 }) { Image(item.image) .width(60) .height(60) .borderRadius(8) .objectFit(ImageFit.Cover); Column({ space: 4 }) { Text(item.title) .fontSize(16) .fontWeight(FontWeight.Medium) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }); Text(item.desc) .fontSize(14) .fontColor('#666666') .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis }); Text(item.time) .fontSize(12) .fontColor('#999999'); } .layoutWeight(1) .alignItems(HorizontalAlign.Start) } .width('100%') .padding(16) .backgroundColor(Color.White) .borderRadius(12) } }
BasicDataSource 基类
/** * LazyForEach数据源基类 */ export class BasicDataSource implements IDataSource { private listeners: DataChangeListener[] = []; totalCount(): number { return 0; } getData(index: number): Object { return {}; } 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); } } // ✅ 通知数据新增 notifyDataAdd(index: number): void { this.listeners.forEach(listener => { listener.onDataAdd(index); }); } // ✅ 通知数据删除 notifyDataDelete(index: number): void { this.listeners.forEach(listener => { listener.onDataDelete(index); }); } // ✅ 通知数据变化 notifyDataChange(index: number): void { this.listeners.forEach(listener => { listener.onDataChange(index); }); } // ✅ 通知数据重载 notifyDataReload(): void { this.listeners.forEach(listener => { listener.onDataReloaded(); }); } }
下拉刷新 + 上拉加载
@Component export struct RefreshableList { private dataSource: ItemDataSource = new ItemDataSource(); @State isRefreshing: boolean = false; @State isLoadingMore: boolean = false; build() { Refresh({ refreshing: $$this.isRefreshing }) { List({ space: 12 }) { LazyForEach(this.dataSource, (item: Item) => { ListItem() { this.buildListItem(item); } }, (item: Item) => item.id.toString()) // ✅ 加载更多指示器 if (this.isLoadingMore) { ListItem() { Row() { LoadingProgress() .width(30) .height(30); Text('加载中...') .fontSize(14) .fontColor('#999999') .margin({ left: 8 }); } .width('100%') .height(60) .justifyContent(FlexAlign.Center) } } } .width('100%') .height('100%') .cachedCount(10) .onReachEnd(() => { this.loadMore(); }) } .onRefreshing(() => { this.refresh(); }) } /** * 下拉刷新 */ async refresh(): Promise<void> { // 加载最新数据 const items = await loadItems(0, 20); // 清空旧数据 this.dataSource = new ItemDataSource(); this.dataSource.pushData(items); this.isRefreshing = false; } /** * 上拉加载 */ async loadMore(): Promise<void> { if (this.isLoadingMore) { return; } this.isLoadingMore = true; const currentCount = this.dataSource.totalCount(); const items = await loadItems(currentCount, 20); if (items.length > 0) { this.dataSource.pushData(items); } this.isLoadingMore = false; } @Builder buildListItem(item: Item) { Row() { Text(item.title).fontSize(16); } .width('100%') .padding(16) .backgroundColor(Color.White) } }

关键优化点

1. LazyForEach vs ForEach

ForEachLazyForEach
渲染时机全量渲染按需渲染
内存占用
适用场景<100 条>100 条
性能

2. cachedCount 缓存

List() { LazyForEach(dataSource, ...) } .cachedCount(10) // ✅ 缓存10个列表项 ​ // 工作原理: // 可见5个 + 上方缓存5个 + 下方缓存5个 = 总共15个

3. 提供唯一 key

// ✅ 正确:提供唯一key LazyForEach(dataSource, (item: Item) => { ListItem() { } }, (item: Item) => item.id.toString()) // 唯一key ​ // ❌ 错误:使用index作为key }, (item: Item, index: number) => index.toString()) // 数据顺序变化会出错

4. 减少组件层级

// ❌ 层级深(6层) Column() { Row() { Column() { Row() { Column() { Text('内容') // 第6层 } } } } } ​ // ✅ 层级浅(2层) Row() { Text('内容') // 第2层 }

最佳实践

1. 分页加载

class PaginatedDataSource extends BasicDataSource { private items: Item[] = []; private pageSize: number = 20; private currentPage: number = 0; private hasMore: boolean = true; async loadNextPage(): Promise<void> { if (!this.hasMore) { return; } const items = await loadItems(this.currentPage, this.pageSize); if (items.length < this.pageSize) { this.hasMore = false; } this.items.push(...items); this.currentPage++; this.notifyDataReload(); } }

2. 图片懒加载

@Builder buildListItem(item: Item) { Row() { // ✅ 图片设置合适大小,避免内存浪费 Image(item.image) .width(60) .height(60) .objectFit(ImageFit.Cover) .interpolation(ImageInterpolation.Low) // 低质量插值 } }

3. 复杂列表项优化

// ✅ 使用@Reusable实现组件复用 @Reusable @Component struct ReusableListItem { @State item: Item | null = null; // ✅ aboutToReuse在复用时调用 aboutToReuse(params: Record<string, Object>): void { this.item = params.item as Item; } build() { if (this.item) { Row() { Text(this.item.title); } } } }

常见问题

Q1: 数据更新后列表不刷新?

// ❌ 错误:直接修改数组 this.items[0].title = 'new'; // UI不更新 // ✅ 正确:通知数据源 this.dataSource.notifyDataChange(0); // 通知第0项变化

Q2: 如何实现列表项删除动画?

ListItem() { this.buildListItem(item); } // ✅ 添加删除动画 .transition(TransitionEffect.OPACITY .animation({ duration: 300 }) .combine(TransitionEffect.translate({ x: -100 })) )

Q3: 如何监控列表性能?

List() { LazyForEach(dataSource, ...) } .onScrollIndex((start, end) => { console.info(`可见范围: ${start} - ${end}`); }) .onScroll((scrollOffset, scrollState) => { if (scrollState === ScrollState.Fling) { console.warn('快速滚动中'); } })

性能对比

测试场景: 10000 条数据

方案初始加载时间内存占用滚动帧率
ForEach~8s~500MB20fps
LazyForEach~0.3s~50MB60fps
LazyForEach+cachedCount~0.2s~60MB60fps

结论: LazyForEach 性能提升40 倍!

总结

LazyForEach: 按需渲染,内存占用低 ✅cachedCount: 缓存列表项,滚动流畅 ✅唯一 key: 正确复用组件 ✅分页加载: 避免一次加载过多 ✅减少层级: 简化组件结构 ✅@Reusable: 组件复用优化

掌握这些技巧,可以轻松处理万级数据列表!

参考资料

  • List 组件开发指南
  • LazyForEach 性能优化
http://www.cnnetsun.cn/news/18643.html

相关文章:

  • GPTBots.ai:从零开始构建企业级AI智能体,无需代码经验
  • 洗车行业小程序源码系统一体化智能后台,让管理清晰高效
  • OPENCV(python)--初学之路(十七)二进制鲁棒独立(BRIEF)和定向快速和轮换(ORB)
  • 为什么说PHP程序员一定要学会自我慈悲?
  • Blender终极指南:如何快速导入虚幻引擎PSK和PSA文件
  • 31、深入探索EXT2文件系统:操作、遍历与实现
  • C盘爆满急救指南:安全删除虚拟内存全流程
  • 银河麒麟桌面操作系统V10 SP1 编译ffmpeg-6.1
  • VisionReward-Image终极解析:重塑AI视觉内容的质量评估范式
  • 智能获客系统深度评测与选型指南 2026五款热门获客平台
  • GPT-5.2实战评测:从“聊天“到“干活“,AI助手进化史
  • 算力直降48%:Moonlight-16B凭什么改写大模型效率规则?
  • 终极代码质量检查:TscanCode如何帮助团队提升开发效率的完整指南
  • DLT Viewer终极指南:从入门到精通的嵌入式日志分析完整教程
  • 如何快速批量下载TikTok封面:完整操作指南
  • nvm-desktop终极指南:高效管理Node.js版本控制方案
  • Django开发效率翻倍:5个必知技巧
  • 开源大模型微调与部署实战指南:从零开始掌握LLaMA Factory工具全流程
  • Windows Cleaner:5分钟彻底解决C盘空间不足的终极方案
  • Python + Ursina设计一个有趣的3D小游戏
  • 企业级Spring应用启动失败排查实战
  • GeoJSON.io:零基础地理数据编辑工具完全指南
  • 特斯拉11月在美销量跌至近四年低点,廉价车型未扭转颓势
  • LangChain4j 比 SolonAI 强在哪?弱在哪?
  • CodeQwen1.5微服务开发实战:从架构设计到部署上线的完整指南
  • 从零玩转RT-Thread(20):为什么需要定时器?——定时器的应用场景
  • eino框架结构化输出解析:从混乱文本到精准数据的魔法转换
  • 小爱音箱终极音乐解放方案:XiaoMusic完整使用指南
  • 从零打造专业级Vue滑块控件:vue-slider-component深度实践指南
  • AnuPpuccin主题深度体验:5个技巧让你的Obsidian笔记焕然一新