长列表性能翻倍:@Reusable 组件复用与复用池实战

前言

LazyForEach + cachedCount 解决了“只渲染可视区”的问题(见 9/3 文章),但当列表快速滑动时,频繁创建/销毁自定义组件依然会卡顿、掉帧。鸿蒙提供了 @Reusable 装饰器 + reuseId,让组件像“对象池”一样被回收复用。本文讲清它和 LazyForEach 的配合,以及 @Reusable 下状态初始化的正确姿势。

问题描述

典型症状:

  1. 长列表快速滑动时明显掉帧,Profiler 显示大量组件创建。
  2. 给组件加了 @Reusable,结果复用时显示的是上一条的旧数据。
  3. 组件内部有定时器/订阅,复用时重复创建导致内存泄漏或重复回调。
  4. 不同“类型”的列表项被错误复用,布局串味。

这些都和“复用时机”与“状态重置”有关。

细节解析

@Reusable 的工作机制:当组件滑出可视区,系统不立即销毁它,而是放进复用池;滑入新位置需要同类型组件时,直接从池里取一个,调用 aboutToReuse(params) 让你更新数据,而不是重新走 aboutToAppear/build。

关键 API:

  • @Reusable:标记组件可被复用。
  • reuseId:在 LazyForEachitemGenerator 里给组件设 reuseId(相同 reuseId 才能互相同复用)。不同类型列表项用不同 reuseId,避免错配。
  • aboutToReuse(params):复用时调用,用来把新数据写进状态,替代 aboutToAppear 里的一次性初始化。
  • aboutToRecycle():即将被回收进池时调用,适合做清理(取消订阅、停定时器)。

LazyForEach 的关系:LazyForEach 负责“创建哪些”,@Reusable 负责“创建出来的能否复用”。两者互补,一起上才完整。

高频坑

  • 复用后显示旧数据 → 没在 aboutToReuse 里更新状态。
  • 复用时定时器叠加 → 没在 aboutToRecycle 里清理。
  • reuseId 不设或设错 → 不同类型互相复用导致样式错乱。

示例代码

// 可复用的列表项
@Reusable
@Component
struct NewsItem {
  @State title: string = ''
  @State desc: string = ''
  private timer?: number

  aboutToReuse(params: Record<string, Object>) {
    // 复用时用新数据覆盖旧状态(关键)
    this.title = params.title as string
    this.desc = params.desc as string
  }

  aboutToRecycle() {
    // 回收前清理,避免复用后叠加
    if (this.timer) { clearInterval(this.timer); this.timer = undefined }
  }

  aboutToAppear() {
    // 仅首次创建时的初始化(复用不会再次进入)
    this.timer = setInterval(() => {}, 1000) as unknown as number
  }

  build() {
    Column({ space: 4 }) {
      Text(this.title).fontSize(16).fontWeight(FontWeight.Bold)
      Text(this.desc).fontSize(13).fontColor('#666')
    }
    .width('100%')
    .padding(12)
    .backgroundColor('#fff')
  }
}

// 列表页
@Entry
@Component
struct ListPage {
  @State data: Array<{ id: number, title: string, desc: string }> = []

  build() {
    List() {
      LazyForEach(this.dataSource, (item) => {
        ListItem() {
          NewsItem({ title: item.title, desc: item.desc })
            .reuseId('news')   // 同类型复用标识
        }
      }, (item) => item.id.toString())
    }
    .cachedCount(5)   // 配合 @Reusable 预渲染缓冲
    .width('100%')
    .height('100%')
  }
}

要点:

  • reuseId('news') 让所有新闻项共享一个复用池;如果有“广告项”用 reuseId('ad') 区分,互不串。
  • aboutToReuse 必须更新全部会变化的 @State,否则复用的组件会带着上一条的数据。
  • aboutToRecycle 做反向清理,和 aboutToAppear 成对。

总结

  • 长列表卡顿先上 LazyForEach;还不够顺滑就加 @Reusable
  • @Reusable 靠“复用池”省去重复创建,aboutToReuse 负责刷新数据,aboutToRecycle 负责清理。
  • reuseId 隔离不同类型的列表项,避免错配。
  • aboutToAppear 只做一次性初始化,复用不会重入;状态更新一律放 aboutToReuse
  • 配合 cachedCount 效果最佳。

把“组件复用”和“懒加载”组合起来,长列表性能通常能提升数倍,滑动如丝般顺滑。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐