文章目录
-
- 每日一句正能量
- 摘要
- 一、为什么需要页面预加载与懒加载
- [二、ForEach vs LazyForEach:渲染机制深度解析](#二、ForEach vs LazyForEach:渲染机制深度解析)
-
- [2.1 ForEach 全量渲染的问题](#2.1 ForEach 全量渲染的问题)
- [2.2 LazyForEach 按需渲染的优势](#2.2 LazyForEach 按需渲染的优势)
- [2.3 核心对比](#2.3 核心对比)
- [三、LazyForEach 懒加载实战:从数据源到组件复用](#三、LazyForEach 懒加载实战:从数据源到组件复用)
-
- [3.1 实现 IDataSource 接口](#3.1 实现 IDataSource 接口)
- [3.2 定义数据模型与列表项组件](#3.2 定义数据模型与列表项组件)
- [3.3 构建懒加载列表页面](#3.3 构建懒加载列表页面)
- [3.4 组件复用 (@Reusable) 机制](#3.4 组件复用 (@Reusable) 机制)
- [四、页面路由预加载:Navigation 与 Router 的预加载策略](#四、页面路由预加载:Navigation 与 Router 的预加载策略)
-
- [4.1 Router 预加载](#4.1 Router 预加载)
- [4.2 Navigation 组件预加载](#4.2 Navigation 组件预加载)
- [4.3 基于用户行为的智能预加载](#4.3 基于用户行为的智能预加载)
- 五、图片与资源的懒加载策略
-
- [5.1 图片懒加载](#5.1 图片懒加载)
- [5.2 动态模块懒加载](#5.2 动态模块懒加载)
- 六、性能调优与最佳实践
-
- [6.1 cachedCount 的合理设置](#6.1 cachedCount 的合理设置)
- [6.2 键值生成函数的最佳实践](#6.2 键值生成函数的最佳实践)
- [6.3 精准通知 vs 全量刷新](#6.3 精准通知 vs 全量刷新)
- [6.4 性能优化效果对比](#6.4 性能优化效果对比)
- 七、实战案例:电商应用全链路优化
-
- [7.1 首页推荐流](#7.1 首页推荐流)
- [7.2 商品列表页](#7.2 商品列表页)
- [7.3 商品详情页](#7.3 商品详情页)
- [7.4 购物车页](#7.4 购物车页)
- 八、总结

每日一句正能量
学习从来不是一件一劳永逸的事情,它应该是人生任何阶段都不可或缺的一种能力。
在快速变化的时代,失去学习能力的人,会逐渐失去应对新问题的能力。
摘要
摘要 :在HarmonyOS应用开发中,页面性能直接决定用户体验的成败。当列表数据量达到成百上千条时,传统的
ForEach全量渲染会导致启动缓慢、内存暴涨、滑动卡顿等严重性能问题。本文将从底层渲染原理出发,深入剖析LazyForEach懒加载机制、组件复用 (@Reusable) 策略、页面路由预加载 (router.preloadUrl) 以及资源懒加载等核心技术,结合电商应用实战案例,提供一套完整的HarmonyOS页面性能优化方案,助你打造启动快、内存稳、滑动流畅的高性能应用。
一、为什么需要页面预加载与懒加载
在移动应用开发中,性能优化是一个永恒的话题。当列表数据量达到成百上千条时,如果一次性渲染所有数据,不仅会造成内存浪费,还会导致页面卡顿。以电商应用为例,首页推荐流可能包含数千个商品卡片,商品列表页可能有上万条SKU数据,如果采用传统的全量渲染方式,应用启动时就需要一次性加载所有数据、创建所有组件节点并挂载到组件树上,这在数据量大时会非常耗时,从而导致页面启动时间过长。更严重的是,屏幕可视区外的组件虽然不会显示在屏幕上,但仍然占用内存,在系统高负载下容易导致应用异常退出。
HarmonyOS ArkUI框架提供了完善的预加载与懒加载能力,通过 LazyForEach 按需渲染、@Reusable 组件复用、cachedCount 预加载缓冲以及路由预加载等技术手段,可以将应用的启动时间缩短30%-50%,内存占用降低60%以上,滑动帧率稳定保持在60fps。
二、ForEach vs LazyForEach:渲染机制深度解析
2.1 ForEach 全量渲染的问题
ForEach 是ArkUI中最基础的循环渲染方式,其核心逻辑是全量创建组件。具体渲染过程如下:
- 一次性加载全量数据:从数据源加载所有数据到内存;
- 全量创建组件:为每一条数据创建对应的组件节点,并全部挂载在组件树上;
- 仅渲染可视区:列表内容显示时,只渲染屏幕可视区内的组件,可视区外的组件已完成创建但处于隐藏状态。
这种机制在数据量较少时(50条以内)性能尚可接受,但当数据量超过100条甚至更多时,会出现严重的性能瓶颈:启动耗时增加、内存峰值暴涨、滑动卡顿掉帧,极端情况下甚至导致应用崩溃。
2.2 LazyForEach 按需渲染的优势
LazyForEach 是HarmonyOS针对长列表场景推出的专属性能优化方案 ,其核心价值用一句话概括:按需创建组件、自动回收资源,用极低的内存占用实现千条/万条数据的流畅滑动。
LazyForEach 的渲染过程如下:
- 按需加载数据:根据屏幕可视区能够容纳的组件数量,仅加载可视区及其附近少量预加载区域的数据;
- 按需创建组件:仅创建可视区和预加载区域的组件节点,构建出一棵短小的组件树;
- 动态加载与回收:当用户滑动列表时,即将进入屏幕的组件被创建和渲染,滑出屏幕的组件进入复用池或被回收,内存始终维持在一个稳定的低水平。

2.3 核心对比
| 对比维度 | ForEach | LazyForEach |
|---|---|---|
| 渲染机制 | 一次性渲染所有项 | 仅渲染可视区域内的数据项 |
| 内存占用 | 与数据量成正比 | 仅占用可视区附近缓存 |
| 适用场景 | 少量简单数据项(<50) | 大数据量列表(100+) |
| 数据更新效率 | 全量更新 | 局部更新(只重绘变化的项) |
| 滚动性能 | 可能卡顿 | 流畅(动态加载卸载) |
| 数据源 | 普通数组 | 实现 IDataSource 接口 |
三、LazyForEach 懒加载实战:从数据源到组件复用
3.1 实现 IDataSource 接口
LazyForEach 要求提供一个实现了 IDataSource 接口的数据源对象。以下是一个通用的数据源基类实现:
typescript
// datasource/BasicDataSource.ets
export class BasicDataSource<T> implements IDataSource {
private listeners: DataChangeListener[] = [];
private originDataArray: T[] = [];
public totalCount(): number {
return this.originDataArray.length;
}
public getData(index: number): T {
return this.originDataArray[index];
}
public registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
this.listeners.push(listener);
}
}
public unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
this.listeners.splice(pos, 1);
}
}
// 数据操作方法
public pushData(data: T): void {
this.originDataArray.push(data);
this.notifyDataAdd(this.originDataArray.length - 1);
}
public deleteData(index: number): void {
this.originDataArray.splice(index, 1);
this.notifyDataDelete(index);
}
public moveData(from: number, to: number): void {
const temp = this.originDataArray.splice(from, 1);
this.originDataArray.splice(to, 0, temp[0]);
this.notifyDataMove(from, to);
}
public notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index);
});
}
public notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index);
});
}
public notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index);
});
}
public notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMove(from, to);
});
}
public notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded();
});
}
public getAllData(): T[] {
return this.originDataArray;
}
}
3.2 定义数据模型与列表项组件
typescript
// model/ProductModel.ets
@Observed
export class ProductModel {
id: string;
name: string;
price: number;
imageUrl: string;
description: string;
constructor(id: string, name: string, price: number, imageUrl: string, description: string) {
this.id = id;
this.name = name;
this.price = price;
this.imageUrl = imageUrl;
this.description = description;
}
}
// components/ProductCard.ets
import { ProductModel } from '../model/ProductModel';
@Reusable
@Component
export struct ProductCard {
@ObjectLink product: ProductModel;
aboutToAppear(): void {
console.info(`[ProductCard] 组件创建 -> ${this.product.name}`);
}
aboutToDisappear(): void {
console.info(`[ProductCard] 组件消失 -> ${this.product.name}`);
}
aboutToRecycle(): void {
console.info(`[ProductCard] 组件回收 -> ${this.product.name}`);
}
aboutToReuse(params: Record<string, ESObject>): void {
console.info(`[ProductCard] 组件复用 -> ${this.product.name}`);
}
build() {
Column({ space: 8 }) {
// 商品图片(懒加载)
Image(this.product.imageUrl)
.width('100%')
.height(120)
.objectFit(ImageFit.Cover)
.borderRadius(8)
.autoResize(true)
Text(this.product.name)
.fontSize(14)
.fontWeight(FontWeight.Medium)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
Text(`¥${this.product.price}`)
.fontSize(16)
.fontColor('#FF4400')
.fontWeight(FontWeight.Bold)
.width('100%')
Text(this.product.description)
.fontSize(12)
.fontColor('#999999')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
}
.width('100%')
.padding(12)
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 4, color: '#1A000000', offsetY: 2 })
}
}
3.3 构建懒加载列表页面
typescript
// pages/ProductListPage.ets
import { BasicDataSource } from '../datasource/BasicDataSource';
import { ProductModel } from '../model/ProductModel';
import { ProductCard } from '../components/ProductCard';
@Entry
@Component
struct ProductListPage {
private productDataSource: BasicDataSource<ProductModel> = new BasicDataSource<ProductModel>();
private cachedCount: number = 6; // 预加载数量
private pageSize: number = 20;
private currentPage: number = 1;
@State isLoading: boolean = false;
@State hasMore: boolean = true;
aboutToAppear(): void {
this.loadData();
}
async loadData(): Promise<void> {
if (this.isLoading || !this.hasMore) return;
this.isLoading = true;
// 模拟网络请求
const newData = await this.fetchProducts(this.currentPage, this.pageSize);
if (newData.length === 0) {
this.hasMore = false;
} else {
newData.forEach(item => this.productDataSource.pushData(item));
this.currentPage++;
}
this.isLoading = false;
}
private async fetchProducts(page: number, size: number): Promise<ProductModel[]> {
return new Promise((resolve) => {
setTimeout(() => {
const data: ProductModel[] = [];
for (let i = 0; i < size; i++) {
const id = `${page}-${i}`;
data.push(new ProductModel(
id,
`商品 ${id}`,
Math.floor(Math.random() * 1000 + 10),
'https://example.com/image.jpg',
'这是一段商品描述文本'
));
}
resolve(data);
}, 500);
});
}
build() {
Column() {
// 标题栏
Row() {
Text('商品列表')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor('#FFFFFF')
// 使用 Refresh 实现下拉刷新
Refresh({ refreshing: $$this.isLoading }) {
Grid() {
LazyForEach(
this.productDataSource,
(item: ProductModel) => {
GridItem() {
ProductCard({ product: item })
}
},
// 键值生成函数:必须返回唯一稳定的值
(item: ProductModel) => item.id
)
}
.cachedCount(this.cachedCount)
.columnsTemplate('1fr 1fr')
.columnsGap(12)
.rowsGap(12)
.padding(16)
.layoutWeight(1)
.onReachEnd(() => {
// 滑动到底部加载更多
this.loadData();
})
}
.onRefreshing(() => {
// 下拉刷新
this.productDataSource = new BasicDataSource<ProductModel>();
this.currentPage = 1;
this.hasMore = true;
this.loadData().then(() => {
this.isLoading = false;
});
})
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
}
3.4 组件复用 (@Reusable) 机制
@Reusable 装饰器是HarmonyOS应用框架提供的组件复用能力。当列表项滑出屏幕时,对应的UI组件不会立即销毁,而是进入复用池等待重新使用。当新的列表项需要渲染时,框架优先从复用池中获取可用组件,然后更新其数据绑定,从而节约组件重新创建的时间。

组件复用的关键生命周期:
aboutToRecycle():组件即将被回收时调用,可在此清理资源;aboutToReuse(params):组件从复用池取出复用时调用,可在此更新数据。
重要提示 :在 aboutToReuse 中对 @Link、@ObjectLink 等自动更新的状态变量赋值,可能触发不必要的组件刷新,通常无需手动处理数据更新,框架会自动绑定新数据。
四、页面路由预加载:Navigation 与 Router 的预加载策略
4.1 Router 预加载
HarmonyOS提供了 router.preloadUrl() API,允许开发者在合适的时机预加载目标页面,从而加速页面切换:
typescript
import { router } from '@kit.ArkUI';
@Entry
@Component
struct HomePage {
// 用户浏览商品列表时,预加载商品详情页
onProductHover(productId: string): void {
// 当用户手指悬停在商品卡片上时,预加载详情页
router.preloadUrl({
url: 'pages/ProductDetailPage',
params: { productId: productId }
});
}
// 用户点击商品时,页面已预加载,切换更流畅
onProductClick(productId: string): void {
router.pushUrl({
url: 'pages/ProductDetailPage',
params: { productId: productId }
});
}
}
4.2 Navigation 组件预加载
对于使用 Navigation 组件的场景,可以通过 NavPathStack 的预加载能力优化:
typescript
import { NavPathStack } from '@kit.ArkUI';
@Entry
@Component
struct NavigationDemo {
@State pathStack: NavPathStack = new NavPathStack();
aboutToAppear(): void {
// 预加载常用页面
this.pathStack.preload('ProductDetailPage', { productId: 'default' });
}
build() {
Navigation(this.pathStack) {
// 页面内容
}
}
}
4.3 基于用户行为的智能预加载
更高级的预加载策略是基于用户行为预测进行智能预加载:
typescript
@Entry
@Component
struct SmartPreloadPage {
private preloadTimer: number = -1;
// 用户停留超过500ms时预加载
onItemHover(item: ProductModel): void {
if (this.preloadTimer !== -1) {
clearTimeout(this.preloadTimer);
}
this.preloadTimer = setTimeout(() => {
router.preloadUrl({
url: 'pages/ProductDetailPage',
params: { productId: item.id }
});
}, 500);
}
onItemLeave(): void {
if (this.preloadTimer !== -1) {
clearTimeout(this.preloadTimer);
this.preloadTimer = -1;
}
}
}

五、图片与资源的懒加载策略
5.1 图片懒加载
对于长列表中的图片,除了组件懒加载外,还应启用图片本身的懒加载:
typescript
Image(this.product.imageUrl)
.width('100%')
.height(120)
.objectFit(ImageFit.Cover)
.autoResize(true) // 自动调整分辨率
.syncLoad(false) // 异步加载,不阻塞UI
.interpolation(ImageInterpolation.High) // 高质量插值
5.2 动态模块懒加载
对于大型功能模块,可以使用动态 import() 实现按需加载:
typescript
// utils/ModuleLoader.ets
export class ModuleLoader {
private static modules: Map<string, any> = new Map();
static async load(moduleName: string): Promise<any> {
if (this.modules.has(moduleName)) {
return this.modules.get(moduleName);
}
const module = await import(moduleName);
this.modules.set(moduleName, module);
return module;
}
}
// 使用示例
async onFeatureButtonClick() {
const heavyModule = await ModuleLoader.load('@ohos/heavyFeature');
heavyModule.execute();
}
六、性能调优与最佳实践
6.1 cachedCount 的合理设置
cachedCount 控制预加载的列表项数量,设置过小会导致快速滑动时出现白块,设置过大会增加内存占用。推荐值:
- List/Grid :根据单屏可见项数量设置,一般为
visibleCount * 1.5; - WaterFlow :由于布局不规则,建议设置稍大,如
visibleCount * 2; - Swiper :通常设置为
1或2。
6.2 键值生成函数的最佳实践
LazyForEach 的第三个参数 keyGenerator 必须返回唯一且稳定的键值:
typescript
// ✅ 正确:使用数据的唯一ID
(item: ProductModel) => item.id
// ❌ 错误:使用索引,数据增删后索引变化会导致渲染错乱
(item: ProductModel, index: number) => index.toString()
6.3 精准通知 vs 全量刷新
数据更新时应使用精准通知方法,避免全量刷新导致列表重建:
typescript
// ✅ 精准通知:只更新变化的项
this.dataSource.notifyDataChange(index);
this.dataSource.notifyDataAdd(index);
this.dataSource.notifyDataDelete(index);
// ❌ 避免:全量刷新会导致整个列表重建
this.dataSource.notifyDataReload();
6.4 性能优化效果对比
通过实际测试,在包含1000条商品数据的长列表场景中,采用本文所述的优化方案后:

| 指标 | 优化前 (ForEach) | 优化后 (LazyForEach) | 提升 |
|---|---|---|---|
| 启动内存 | 85 MB | 35 MB | 降低 59% |
| 峰值内存 | 320 MB | 95 MB | 降低 70% |
| 启动时间 | 2.8s | 0.9s | 缩短 68% |
| 快速滑动帧率 | 28 fps | 58 fps | 提升 107% |
| 普通滑动帧率 | 42 fps | 60 fps | 提升 43% |
七、实战案例:电商应用全链路优化
以下是一个电商应用从首页到详情页的全链路预加载与懒加载架构:

7.1 首页推荐流
首页采用 LazyForEach + Grid 实现瀑布流布局,cachedCount 设置为6,商品卡片使用 @Reusable 装饰器,图片启用异步加载。
7.2 商品列表页
用户点击分类入口后,列表页使用 LazyForEach + Grid 展示商品,配合 onReachEnd 实现分页加载。同时,当用户手指悬停在商品卡片超过500ms时,预加载商品详情页。
7.3 商品详情页
详情页的评论列表使用 LazyForEach + List 实现,大图采用渐进式加载策略,先显示低分辨率占位图,再加载高清图。
7.4 购物车页
购物车列表使用 LazyForEach + List,配合 @StorageLink 实现状态持久化,批量编辑操作通过精准通知更新UI。
八、总结
本文从HarmonyOS ArkUI的底层渲染机制出发,系统性地讲解了页面预加载与懒加载的完整技术方案:
LazyForEach懒加载 :通过IDataSource接口实现按需渲染,配合cachedCount预加载缓冲,解决大数据量列表的性能瓶颈;@Reusable组件复用:利用组件复用池减少组件创建开销,保持滑动流畅;- 路由预加载 :通过
router.preloadUrl()和Navigation预加载能力,加速页面切换; - 资源懒加载 :图片异步加载、动态模块
import(),降低启动负担; - 精准数据通知 :使用
onDataChange、onDataAdd等方法局部刷新,避免全量重建。
核心结论 :LazyForEach + @Reusable + 精准通知三者组合,是鸿蒙高性能列表的标准解法。超过100条的列表,无条件用 LazyForEach 替代 ForEach。
转载自:https://blog.csdn.net/u014727709/article/details/163313782
欢迎 👍点赞✍评论⭐收藏,欢迎指正