HarmonyOS 7 + ArkUI + LazyForEach 技术干货:长列表渲染优化、状态隔离与滚动性能治理【鸿蒙心迹】
很多人做列表优化,第一反应是“卡了就加缓存”。但真进入项目以后,你会发现列表性能并不是一个点问题,而是一串连锁反应:数据量、渲染方式、组件复用、状态传递、滚动监测,最后都会影响用户手感。

一、长列表最怕的,不是数据多,而是渲染策略没收住
做资讯流、评论流、商品流、消息流时,大家都知道长列表容易卡。
但如果只把问题归结成“数据多”,其实不够准确。
在 ArkUI 里,真正让长列表开始发沉的,通常不是数据本身,而是下面几件事叠在一起:
- 列表项创建过多;
- 组件重复销毁和重建;
- 状态更新牵动整列刷新;
- item 没有稳定标识;
- 滚动过程中首屏和非首屏逻辑混在一起;
- 页面上还有图片、统计、动画等额外负担。
也就是说,长列表治理真正解决的,不是“数据多不多”,而是每次滚动时,系统到底做了多少没必要的事。
我后来把这个主题拆成四块去看:
- 按需加载;
- 列表项复用;
- 状态隔离;
- 滚动性能监测。
只要这四块梳顺,列表体验通常就会有明显变化。
二、第一步不是调参数,而是先换成更合适的渲染方式
很多人列表一开始就写成普通循环渲染,功能没问题,但数据一上来就会开始吃力。
如果场景本身就是长列表,第一步更应该先考虑按需渲染。在 ArkUI 里,这个点最典型的就是 LazyForEach。
它的价值不只是“写法不一样”,而是:
- 不会一次性把所有项都建出来;
- 能结合列表滚动,只处理当前需要显示的内容;
- 后续更方便接复用与性能统计。
我这次的基础写法如下:
import { ArticleItem, getArticleList } from '../model/ArticleData'
@State private articles: ArticleItem[] = []
aboutToAppear() {
this.articles = getArticleList(200)
}
build() {
List({ space: 12 }) {
LazyForEach(this.articles, (item: ArticleItem, index: number) => {
ReuseItem({ item })
}, (item: ArticleItem) => item.id)
}
}
真正关键的有两个点:
- 用
LazyForEach替代普通批量渲染; - 给每个 item 提供稳定的
item.id。
很多时候,性能问题不是因为框架不行,而是因为你没有给框架稳定工作的条件。
三、稳定的 itemKey,比想象中重要得多
列表优化里有一个很容易被低估的点:唯一标识。
如果列表项没有稳定的 key,系统很难准确判断哪些项是“同一个对象”,哪些项需要真正重建。结果就是:
- 本来可以复用的项被重新创建;
- 状态错位;
- 滚动中出现额外的重绘和卡顿。
所以我在列表数据设计上,会优先保证每个 item 都有固定唯一标识,不拿 index 临时凑数。
这一点在代码里看起来只是最后一个回调:
LazyForEach(this.articles, (item: ArticleItem) => {
ReuseItem({ item })
}, (item: ArticleItem) => item.id)
但它带来的收益很实在:
- 复用关系更稳定;
- diff 更可靠;
- 项目后面即使插入、删除、排序变化,列表也没那么容易乱。
四、长列表真正省下来的,不是时间,而是“重复工作”
很多优化文章喜欢直接上 FPS、耗时、掉帧率,我也会看这些数字。
但从工程角度说,我更在意的是:有没有减少重复工作。
比如这几个动作,如果没控制好,就会在滚动里被无限放大:
- 列表项重复创建;
- 组件重复销毁;
- 父组件状态变化牵动全量刷新;
- 每次小更新都触发整列表渲染。
所以我的第二层治理,是把“可复用”“可局部更新”“可状态隔离”这几件事做明确。
最直接的方式,是把列表项组件抽出来,并明确声明它是可复用组件:
@Reusable
@Component
export struct ReuseItem {
@Prop item: ArticleItem
build() {
Row() {
Column() {
Text(this.item.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.maxLines(2)
Text(this.item.desc)
.fontSize(14)
.fontColor('#666666')
.maxLines(2)
}
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
}
}
这类优化的意义并不神秘:
- 同类型列表项有机会复用;
- 创建和销毁压力会小很多;
- 滚动时的抖动通常也会跟着下降。

我在这张 DevEco 图里故意用红色标了三类读者最该关注的点:
LazyForEach的按需加载;itemKey的稳定标识;@Reusable的组件复用。
底部日志区域我也保留了“优化前 / 优化后”的对照,这比只放一段代码更容易让人形成结果感。
五、状态隔离,才是避免“无关刷新”的关键
长列表还有一个特别常见的问题:看起来是某一项状态更新,实际上整列都在跟着重刷。
这种问题初期往往不明显,一旦列表够长、每项又有图片、按钮、角标、阅读状态等内容时,问题马上就会放大。
所以我更强调状态隔离。
什么叫状态隔离?
简单说,就是:
- 属于某个 item 的状态,尽量只在该 item 内管理;
- 父级尽量只管理必要的列表数据;
- 小更新尽量局部传播,不让整个列表陪跑。
比如点赞状态、展开状态、选中状态,如果本来就是局部行为,就别让它们一路往上抬成全局刷新触发器。
这件事做好了,很多时候你不用做特别复杂的性能技巧,列表就已经会顺很多。
六、没有数据监测,优化很容易变成“体感玄学”
列表优化特别容易陷入一个误区:
我觉得现在滑起来更顺了。
但“觉得”不够。
做技术干货,最好把结果落到更可验证的指标上。比如:
- 丢帧率;
- 平均渲染耗时;
- FPS;
- 首屏耗时;
- 内存占用;
- 可见项数量;
- 复用项数量。
所以我会在列表页里加一个监测器,把这些关键数据收起来。最基础的做法,是在滚动回调里记录首尾 index,并结合日志持续输出:
class PerformanceMonitor {
start() {
console.info('start monitor')
}
onScroll(first: number, last: number) {
console.info(`visible items: ${first} - ${last}`)
}
}
当然,实际项目可以更丰富,比如配合 profiler、页面埋点或者内部统计模块。但思路是一致的:
- 优化前先看基线;
- 优化后再看变化;
- 不是只看一次截图,而是看整体趋势。

这张手机图承担的是“实时看板”角色。我让它重点展示三块:
- FPS;
- 首屏耗时;
- 内存占用。
同时又在文章列表区用红色虚线框和箭头解释“列表项复用”和“流畅滚动体验”。这样它就不只是展示页面,而是在讲解优化思路。
七、最终要让优化结果变得“可解释”
只说“更快了”还不够。更好的写法,是把优化前后拆解给读者看:
- 掉帧率为什么下降;
- 平均渲染耗时为什么缩短;
- 内存为什么更平稳;
- 哪些配置真正起了作用。
所以我又整理了一个“性能治理详情页”,专门把优化结果和配置开关放在一起。这样做有两个好处:
- 对读者来说,看得清楚;
- 对开发者自己来说,后续排查也方便。

这张图里我比较满意的是几处红色说明:
- 丢帧率显著下降;
- 渲染耗时降低;
- FPS 接近满帧;
- 开启稳定
itemKey; - 开启差量更新;
- 开启组件复用;
- 启用状态隔离。
你会发现,这样的图放在文章里特别适合。因为它把“优化动作”和“优化结果”同时解释掉了。
八、这类主题真正值得写透的地方
我做完这次长列表治理后,一个非常明确的感受是:
性能优化不是某个神奇参数,而是一套减少无效工作的策略。
真正能让列表变顺的,通常不是只开一个开关,而是下面这些点合在一起:
- 用更合适的渲染方式承接长列表;
- 保证 itemKey 稳定;
- 让列表项可复用;
- 把状态隔离在合理边界;
- 用监测数据验证优化结果。
这些事情拆开看都不复杂,但只要有一个环节没收住,滚动手感就很容易开始发沉。
九、本文小记
如果让我用一句话概括这篇文章,我会写成:
长列表优化的本质,不是“把页面做快一点”,而是让渲染、状态和滚动各自回到更合理的位置。
这次我自己最认可的几个工程判断是:
- 长列表优先考虑按需渲染;
itemKey一定要稳定;- 组件复用是很实用的基础优化;
- 状态隔离能显著减少无关刷新;
- 监测数据比“体感更顺”更有说服力。
如果你接下来也要写 HarmonyOS 的性能优化题材,我会建议你别只停留在“优化前后对比”这一层,而是把渲染方式、组件复用、状态隔离和监测指标一起讲清楚。这样文章不只是有结果,也会更有技术味。
更多推荐





所有评论(0)