Skip to content

列表性能优化

列表是鸿蒙性能优化的核心场景,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 数据源

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 分页"