Appearance
列表性能优化
列表是鸿蒙性能优化的核心场景,LazyForEach 是关键。
1. ForEach vs LazyForEach
1.1 ForEach(全量渲染)
typescript
// ❌ 大数据量性能差
@Entry
@Component
struct BadList {
@State data: Array<Item> = generate10000Items()
build() {
List() {
ForEach(this.data, (item: Item) => {
ListItem({ item })
})
}
}
}
// 问题:10000 条数据创建 10000 个组件
// - 内存占用 100MB+
// - 首屏渲染慢(3-5 秒)
// - 滚动卡顿1.2 LazyForEach(懒加载渲染)
typescript
// ✅ 推荐:LazyForEach 只渲染可见区域
@Entry
@Component
struct GoodList {
@State data: Array<Item> = generate10000Items()
build() {
List() {
LazyForEach(this.data, (item: Item) => {
ListItem({ item })
}, (item: Item) => item.id) // 唯一 key
}
}
}
// 优化效果:
// - 首屏渲染 < 100ms(只渲染约 10 个组件)
// - 内存占用 < 10MB
// - 滚动流畅2. LazyForEach 数据源
2.1 ObjectLink 数据源
typescript
// 使用 @ObjectLink 实现双向绑定的懒加载数据源
class ItemDataSource implements IDataSource {
private observer: DataChangeObserver | undefined = undefined
private dataList: Array<Item> = []
getData(): Array<Item> {
return this.dataList
}
getObserver(): IDataSourceObserver {
return this.observer
}
addDataChangeObserver(observer: DataChangeObserver): void {
this.observer = observer
}
removeDataChangeObserver(observer: DataChangeObserver): void {
this.observer = undefined
}
// 数据变化通知
notifyDataChanged(): void {
this.observer?.onDataChange()
}
// 数据变化范围通知
notifyDataRangeChange(start: number, end: number): void {
this.observer?.onDataRangeChange(start, end)
}
}
@Entry
@Component
struct DataList {
@ObjectLink items: Array<Item>
@ObjectLink source: ItemDataSource
build() {
List() {
LazyForEach(this.source, (item: Item) => {
ListItem({ item: item })
}, (item: Item) => item.id)
}
}
}2.2 数据刷新
typescript
// LazyForEach 数据变化通知
class ItemDataSource implements IDataSource {
// 刷新单条
updateItem(index: number, item: Item): void {
this.dataList[index] = item
this.notifyDataRangeChange(index, index) // 通知变化范围
}
// 批量刷新
updateItems(start: number, end: number): void {
this.notifyDataRangeChange(start, end)
}
// 新增数据
addItem(item: Item): void {
this.dataList.push(item)
let index = this.dataList.length - 1
this.notifyDataRangeChange(index, index)
}
// 删除数据
deleteItem(index: number): void {
this.dataList.splice(index, 1)
this.notifyDataRangeChange(index, index)
}
}3. 列表项优化
3.1 列表项复用
typescript
// ❌ 错误:每个 ListItem 都创建新数组
@Entry
@Component
struct BadListItem {
@State item: Item = { tags: [] }
build() {
List() {
LazyForEach(this.items, (item: Item) => {
ListItem() {
Row() {
ForEach(item.tags, (tag: string) => {
Text(tag)
})
}
}
})
}
}
}
// ✅ 正确:缓存 Tags 渲染结果
@Component
struct GoodListItem {
@ObjectLink item: Item
private tagCache: Text[] = []
aboutToAppear() {
this.tagCache = this.item.tags.map((tag: string) => Text(tag))
}
build() {
Row() {
ForEach(this.tagCache, (tag: Text) => tag)
}
}
}3.2 图片懒加载
typescript
// 列表项图片懒加载
LazyForEach(this.items, (item: Item) => {
ListItem() {
Row() {
LazyImage(item.avatar) // LazyImage 懒加载
.width(50)
.height(50)
}
}
}, (item: Item) => item.id)4. 分页与无限滚动
typescript
@Entry
@Component
struct InfiniteList {
@State items: Array<Item> = []
@State loading: boolean = false
@State hasMore: boolean = true
aboutToAppear() {
this.loadPage(1)
}
build() {
List() {
LazyForEach(this.items, (item: Item) => {
ListItem() {
Text(item.title)
}
}, (item: Item) => item.id)
// 加载更多指示器
if (this.hasMore) {
ListItem() {
Row() {
if (this.loading) {
LoadingProgress()
} else {
Text('上拉加载更多')
}
}
}
}
}
.onReachEnd(() => {
if (this.hasMore && !this.loading) {
this.loadNextPage()
}
})
}
private async loadPage(page: number): Promise<void> {
this.loading = true
let response = await fetch(`/api/items?page=${page}&size=20`)
let data = await response.json()
if (page === 1) {
this.items = data.items
} else {
this.items.push(...data.items)
}
this.hasMore = data.hasMore
this.loading = false
}
private loadNextPage(): void {
this.loadPage(Math.floor(this.items.length / 20) + 1)
}
}5. 面试高频考点
Q1: LazyForEach 的性能优化原理?
回答:只渲染可见区域(约 10 个组件),滚动时动态创建/销毁。相比 ForEach 全量渲染,首屏快 10 倍+,内存降低 10 倍+。
Q2: LazyForEach 的 key 为什么必须是唯一的?
回答:用于数据变化时精确定位需要更新的组件,避免全量重新渲染。key 不唯一会导致渲染错误。
🐱 小猫提示:列表优化记住 "LazyForEach 必用、唯一 key、数据刷新通知、图片 LazyImage 懒加载、onReachEnd 分页"。