
做鸿蒙开发的时候,长列表是绕不开的场景。不管是信息流、商品列表还是设置页,只要数据量上了百条,滚动卡顿、掉帧就很容易出现。最近在项目里优化一个 500+ 条数据的列表,把 FPS 从 30 拉到了稳定 60,这里把踩过的坑和优化思路整理一下。
问题现象
我们的列表页是商品列表,每个 item 包含一张图片、标题、价格和标签。数据量大概在 500 条左右,初始加载就有明显的白屏和卡顿,快速滚动的时候掉帧严重,低端机上甚至能感觉到明显的撕裂。
一开始我以为是图片加载的问题,加了图片缓存之后效果不明显。后来用 Profiler 一查,发现瓶颈根本不在图片,而是在 List 组件本身------每滚动一屏,都在反复创建和销毁组件,渲染线程压力很大。
优化思路
ArkUI 的 List 组件其实自带懒加载能力,但很多人用的时候没开对参数。核心是三个点:
-
cachedCount :预渲染屏幕外的 item 数量,滚动时不会临时创建
-
layout :用 LinearLayout 而不是默认的,配合 cachedCount 效果更好
-
keyGenerator:给每个 item 指定稳定的 key,减少不必要的组件重建
另外,自定义组件要拆得够细,不要把整个 item 的渲染都塞在一个 build 方法里。拆成子组件之后,配合 @State 和 @Prop,只有变化的部分才会重新渲染。

代码实现
下面是优化后的完整代码,包含了懒加载配置和 item 拆分。
1. 列表组件配置
typescript
// ProductListPage.ets
@Entry
@Component
struct ProductListPage {
@State products: Product[] = [];
private listController: ListController = new ListController();
aboutToAppear() {
this.loadData();
}
build() {
List({ space: 8, controller: this.listController }) {
ForEach(this.products, (item: Product) => {
ListItem() {
ProductItem({ product: item })
}
// 关键:给每个 item 稳定的 key
}, (item: Product) => item.id)
}
// 关键:预渲染 10 个屏外 item
.cachedCount(10)
// 关键:用 LinearLayout 布局,配合缓存效果更好
.layout(LinearLayout)
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5')
}
private loadData() {
// 模拟加载 500 条数据
this.products = Array.from({ length: 500 }, (_, i) => ({
id: `product_${i}`,
title: `商品 ${i}`,
price: Math.floor(Math.random() * 1000) + 100,
image: `https://picsum.photos/200/200?random=${i}`
}));
}
}
2. 拆分 item 组件
typescript
// ProductItem.ets
@Component
struct ProductItem {
@Prop product: Product;
build() {
Row({ space: 12 }) {
// 图片单独渲染
Image(this.product.image)
.width(80)
.height(80)
.borderRadius(8)
.objectFit(ImageFit.Cover)
Column({ space: 4 }) {
// 标题
Text(this.product.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
// 价格
Text(`¥${this.product.price}`)
.fontSize(18)
.fontColor('#ff4400')
.fontWeight(FontWeight.Bold)
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding(12)
.backgroundColor('#ffffff')
.borderRadius(8)
}
}
踩坑总结
1. cachedCount 不是越大越好
一开始我把 cachedCount 设成了 50,结果内存直接爆了。预渲染太多 item 会占用大量内存,反而得不偿失。一般设成 5~10 就够了,具体数值要在真机上测。
2. keyGenerator 一定要传
不传 key 的话,ForEach 默认用索引做 key,数据刷新的时候整个列表都会重新渲染。传了稳定的 id 之后,只有新增或删除的 item 才会重建,性能提升非常明显。
3. 图片要用本地缓存
网络图片每次滚动回来都重新加载,肯定卡。HarmonyOS 7 的 Image 组件默认有内存缓存,但建议自己再加一层磁盘缓存,避免重复请求。
4. 低端机要降帧
优化完之后高端机满帧,但低端机还是偶尔掉帧。这时候可以在低端机上降低图片分辨率,或者关闭一些特效,保证基本流畅度。
效果对比
优化前:500 条数据,快速滚动时 FPS 30~40,明显卡顿。
优化后:同样数据量,FPS 稳定 55~60,快速滚动基本无感。
性能优化这件事,别靠猜,用 Profiler 量出来的瓶颈才是真瓶颈。我们一开始以为是图片的问题,结果折腾了半天图片缓存,最后发现是 List 本身没开缓存。
总结一下就是:List 一定要开 cachedCount,ForEach 一定要传 keyGenerator,item 要拆成独立组件,图片要做缓存。这三点做到位,大部分长列表的性能问题都能解决。