问题描述
在 HarmonyOS 开发中,List 列表是最常用的组件,但数据量大时容易出现性能问题:
- 滚动卡顿,帧率下降
- 加载 1000+ 条数据时崩溃
- 列表项复杂时渲染慢
- 内存占用过高
关键字:List 性能优化、LazyForEach、cachedCount、列表复用
解决方案
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
| ForEach | LazyForEach | |
|---|---|---|
| 渲染时机 | 全量渲染 | 按需渲染 |
| 内存占用 | 高 | 低 |
| 适用场景 | <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 | ~500MB | 20fps |
| LazyForEach | ~0.3s | ~50MB | 60fps |
| LazyForEach+cachedCount | ~0.2s | ~60MB | 60fps |
结论: LazyForEach 性能提升40 倍!
总结
✅LazyForEach: 按需渲染,内存占用低 ✅cachedCount: 缓存列表项,滚动流畅 ✅唯一 key: 正确复用组件 ✅分页加载: 避免一次加载过多 ✅减少层级: 简化组件结构 ✅@Reusable: 组件复用优化
掌握这些技巧,可以轻松处理万级数据列表!
参考资料
- List 组件开发指南
- LazyForEach 性能优化