HarmonyOS 7 实战 06:长列表性能优化,LazyForEach 与组件复用
列表一卡,用户就骂你 App 垃圾
列表是移动端最常见的 UI,也是最容易翻车的。数据少的时候没事,一上千条,滑动就开始掉帧,图片加载不出来,内存飙升。用户不会管你代码写得怎么样,他只看到“这 App 好卡”。
很多人第一反应是“用 LazyForEach 不就行了?” 对,但 LazyForEach 只是第一步。如果你不配合组件复用、图片缓存、数据更新策略,该卡还是卡。
这篇就讲清楚:LazyForEach 怎么用、@Reusable 怎么复用、数据更新怎么不闪屏,以及那些让你滑动掉帧的隐藏坑。
一、LazyForEach:别再用 ForEach 硬扛
ForEach 会一次性创建所有列表项,数据一多,内存和渲染压力都爆炸。LazyForEach 则是按需创建,滑到哪渲染到哪。
但 LazyForEach 不能直接传数组,必须传一个实现了 IDataSource 接口的对象:
typescript
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = [];
private dataArray: string[] = [];
totalCount(): number {
return this.dataArray.length;
}
getData(index: number): string {
return this.dataArray[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);
}
}
// 数据更新方法
public addData(item: string): void {
this.dataArray.push(item);
this.listeners.forEach(listener => listener.onDataAdd(this.dataArray.length - 1));
}
public deleteData(index: number): void {
this.dataArray.splice(index, 1);
this.listeners.forEach(listener => listener.onDataDelete(index));
}
public changeData(index: number, item: string): void {
this.dataArray[index] = item;
this.listeners.forEach(listener => listener.onDataChange(index));
}
}
然后在组件里用:
typescript
@Entry
@Component
struct LongListPage {
private dataSource: BasicDataSource = new BasicDataSource();
aboutToAppear() {
for (let i = 0; i < 10000; i++) {
this.dataSource.addData(`Item ${i}`);
}
}
build() {
List() {
LazyForEach(this.dataSource, (item: string) => {
ListItem() {
Text(item)
.fontSize(16)
.padding(16)
}
}, (item: string) => item)
}
.width('100%')
.height('100%')
.cachedCount(5) // 预加载5个
}
}
cachedCount 控制预加载数量,默认是 1。设大一点滑动更流畅,但内存占用也更高。一般设 3-5 比较合适。
二、@Reusable:组件复用才是性能关键
LazyForEach 解决了“创建多少”的问题,但每次滑动还是会重建组件。如果列表项结构复杂,重建开销很大。
这时候就要用 @Reusable 装饰器,让组件可以被复用:
typescript
@Reusable
@Component
struct ListItemComponent {
@State item: string = '';
aboutToReuse(params: Record<string, Object>): void {
this.item = params.item as string;
}
build() {
Row() {
Image($r('app.media.icon'))
.width(40)
.height(40)
Text(this.item)
.fontSize(16)
.margin({ left: 12 })
}
.padding(16)
}
}
然后在 LazyForEach 里用:
typescript
LazyForEach(this.dataSource, (item: string) => {
ListItem() {
ListItemComponent({ item: item })
}
}, (item: string) => item)
关键在 aboutToReuse。当组件被复用时,会调用这个方法,你在这里更新数据,而不是重新创建组件。这样滑动时就不会频繁触发 aboutToAppear 和 build,性能提升非常明显。
注意: @Reusable 必须配合 LazyForEach 使用才有效果,ForEach 不行。
三、数据更新:别动不动就全量刷新
很多人更新列表数据时,直接 this.dataSource = newDataSource,这会导致整个列表重建,滑动位置丢失,体验极差。
正确做法是用 DataChangeListener 提供的细粒度更新方法:
-
onDataAdd(index):新增一项。 -
onDataDelete(index):删除一项。 -
onDataChange(index):修改一项。 -
onDataMove(from, to):移动一项。
比如删除:
typescript
public deleteData(index: number): void {
this.dataArray.splice(index, 1);
this.listeners.forEach(listener => listener.onDataDelete(index));
}
这样列表只会移除对应项,不会重建整个列表。
坑来了: 如果你在 onDataChange 里传的 index 不对,列表会错乱。比如你删除了第 3 项,但通知的是第 5 项,那 UI 就会显示错误的数据。所以 index 一定要和实际数据操作一致。
四、图片加载:别让图片拖垮列表
列表里如果有图片,加载不当会非常卡。几个优化点:
第一,用 Image 的 syncLoad 属性。 默认是异步加载,滑动快的时候可能出现白块。设置 syncLoad(true) 会同步加载,但会阻塞 UI,只适合小图。
第二,设置 alt 占位图。 图片没加载出来时显示占位图,避免布局跳动:
typescript
Image(this.item.imageUrl)
.alt($r('app.media.placeholder'))
.width(80)
.height(80)
第三,图片缓存。 HarmonyOS 的 Image 组件自带内存缓存,但磁盘缓存需要自己处理。可以用 @ohos.file.fs 把图片下载到沙箱,下次直接从本地读。
第四,控制图片尺寸。 别用原图直接显示,用 objectFit 配合固定宽高,避免解码大图浪费内存。
五、常见坑合集
坑一:key 生成重复。
LazyForEach 的第三个参数是 key 生成器,必须保证每条数据 key 唯一。如果你用 index 当 key,删除数据后 key 会错乱,导致复用错位。要用数据本身的唯一 ID。
坑二:嵌套 List 性能差。
List 里面再套 List,滑动时内外层都会触发渲染,性能极差。尽量用扁平化数据结构,或者用 Grid 代替。
坑三:build 里做复杂计算。
build() 会频繁执行,在里面写排序、过滤、格式化,页面必卡。所有计算提前在 aboutToAppear 或数据更新时做完。
坑四:@State 修饰大数组。
@State 会深度监听数组变化,数据量大时性能很差。用 @State 修饰 IDataSource 对象,而不是数组本身。
六、总结
这篇讲了长列表性能优化的核心手段:LazyForEach、@Reusable、细粒度数据更新和图片优化。核心记住三点:
-
LazyForEach + @Reusable 是黄金组合,缺一不可。
-
数据更新用 onDataAdd/Delete/Change,别全量刷新。
-
key 必须唯一,别用 index。
更多推荐



所有评论(0)