ArkUI 高性能列表懒加载与组件复用实战
ArkUI 高性能列表懒加载与组件复用实战
前言
在 HarmonyOS 应用里,列表是最常见也最容易卡顿的界面。很多开发者一上来用 ForEach 把成百上千条数据一次性塞进 List,结果首屏白屏、滑动掉帧、内存飙升。本文基于华为开发者论坛的真实问题「ArkUI 自定义组件如何实现高性能的列表懒加载与复用」,系统讲解 LazyForEach + cachedCount + @Reusable 这套官方高性能组合,并给出可运行的完整示例。
问题描述
- 使用
ForEach渲染大列表时,所有子组件在首屏就被创建,节点数随数据量线性暴涨; - 滑动时频繁创建/销毁组件,触发大量布局与渲染,造成掉帧;
- 即使数据不变,滚动时也看不到明显的性能改善。
根本原因是:没有懒加载 + 没有组件复用。ArkUI 的 ForEach 是"全量展开",而 LazyForEach 才是"按需创建"。
细节解析
1. LazyForEach:只创建视口内的节点
LazyForEach 接收一个 IDataSource,只在组件进入视口附近时才创建节点,离开后销毁。它要求第二项参数是组件工厂,第三项参数是唯一 key 生成函数(必须稳定,否则复用错乱)。
2. cachedCount:视口外预创建
List 的 cachedCount 控制视口上下额外预创建的节点数。设太小滑动会"现创建"导致卡顿,设太大又吃内存,一般取单屏可见数的 1~2 倍(3~8)。
3. @Reusable:组件复用而不是重建
@Reusable 装饰的自定义组件在被滑出视口后不会销毁,而是进入复用池;再次进入视口时调用 aboutToReuse(params) 更新数据后直接使用。这是滑动流畅的关键。
4. aboutToReuse:复用时的数据刷新
复用组件不会重新走 aboutToAppear,必须用 aboutToReuse 把新数据写回状态变量,否则会显示旧内容。
示例代码
// 数据源:必须实现 IDataSource
export class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = [];
private dataArray: number[] = [];
totalCount(): number { return this.dataArray.length; }
getData(index: number): number { return this.dataArray[index]; }
push(data: number): void {
this.dataArray.push(data);
this.notifyDataAdd(this.dataArray.length - 1);
}
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));
}
}
// 可复用列表项
@Reusable
@Component
struct ReusableListItem {
@State item: number = 0;
// 复用入池前/后由框架调用,用于刷新数据
aboutToReuse(params: Record<string, Object>): void {
this.item = params.item as number;
}
build() {
Row() {
Image($r('app.media.startIcon')).width(40).height(40)
Text('Item #' + this.item)
.fontSize(16)
.layoutWeight(1)
.margin({ left: 12 })
}
.width('100%')
.height(64)
.padding({ left: 16, right: 16 })
}
}
@Entry
@Component
struct HighPerfListPage {
private data: BasicDataSource = new BasicDataSource();
private scroller: ListScroller = new ListScroller();
aboutToAppear(): void {
for (let i = 0; i < 1000; i++) this.data.push(i);
}
build() {
Column() {
List({ scroller: this.scroller }) {
LazyForEach(this.data, (item: number) => {
ListItem() {
ReusableListItem().item(item) // 注意:用 aboutToReuse 接收
}
}, (item: number) => item.toString()) // 稳定的唯一 key
}
.width('100%')
.layoutWeight(1)
.cachedCount(5) // 视口外预创建 5 个节点
}
}
}
提示:上面
ReusableListItem().item(item)写法需配合@Reusable与aboutToReuse;若用构造参数传值,框架会在复用时自动以aboutToReuse注入新参数。
总结
- 大列表一律用
LazyForEach代替ForEach,节点数从"全量"降为"视口+缓存"; cachedCount是滑动流畅度的调节旋钮,按单屏高度取值 3~8;@Reusable+aboutToReuse让组件"复用而非重建",是掉帧的根治手段;- key 生成函数必须稳定唯一,否则会出现数据错位。
按此组合,千级列表也能稳定 60fps。下一篇我们将聚焦「深色模式优雅适配」,敬请关注。
更多推荐



所有评论(0)