HarmonyOS APP开发:组件复用池与@Reusable深度实践

举报
Jack20 发表于 2026/06/23 20:10:17 2026/06/23
【摘要】 HarmonyOS APP开发:组件复用池与@Reusable深度实践📌 核心要点:@Reusable装饰器让组件"退役"后进入复用池而非直接销毁,下次需要同类组件时直接"召回",省去了创建+初始化的开销,是列表滑动场景的性能利器。 一、背景与动机先想象一个场景:你有一个聊天列表,每个列表项就是一个聊天气泡组件。用户上下滑动列表时,列表项不断被创建和销毁——滑出屏幕的组件被销毁,滑入屏幕...

HarmonyOS APP开发:组件复用池与@Reusable深度实践

📌 核心要点:@Reusable装饰器让组件"退役"后进入复用池而非直接销毁,下次需要同类组件时直接"召回",省去了创建+初始化的开销,是列表滑动场景的性能利器。


一、背景与动机

先想象一个场景:你有一个聊天列表,每个列表项就是一个聊天气泡组件。用户上下滑动列表时,列表项不断被创建和销毁——滑出屏幕的组件被销毁,滑入屏幕的组件被创建。

这有什么问题?问题在于组件的创建并不便宜。一个组件从"无"到"有",要经历:分配内存 → 初始化状态 → 执行aboutToAppear回调 → 测量 → 布局 → 绘制。这一整套流程下来,少则几毫秒,多则十几毫秒。如果列表滑动很快,每帧都要创建好几个新组件,那16.6ms的帧预算很快就会被吃光。

那有没有办法让组件"退役"后不销毁,而是放到一个"蓄水池"里,下次需要同类组件时直接捞出来用?有,这就是组件复用池的核心思想。

HarmonyOS通过@Reusable装饰器实现了这个机制。被@Reusable标记的组件,在从组件树中移除时不会立即销毁,而是进入复用池。当下次需要创建同类型的组件时,框架会优先从复用池中取出一个"退役"组件,重新初始化后直接使用,省去了创建的开销。

这就像工厂里的"临时工"——活忙的时候招来用,活少了先待命,下次有活直接上,不用重新培训。效率提升立竿见影。


二、核心原理

2.1 组件复用池工作机制

flowchart TB
    A[组件从组件树移除] --> B{是否标记@Reusable?}
    B -->|| C[直接销毁 💀]
    B -->|| D[进入复用池 🏊]
    D --> E[复用池队列]

    F[需要创建新组件] --> G{复用池中有同类组件?}
    G -->|| H[创建新组件 🆕]
    G -->|| I[从复用池取出 ♻️]
    I --> J[执行aboutToReuse回调]
    J --> K[重新初始化状态]
    K --> L[加入组件树 ✅]

    H --> L

    classDef remove fill:#E74C3C,stroke:#C0392B,color:#fff,font-weight:bold
    classDef destroy fill:#8E44AD,stroke:#6C3483,color:#fff,font-weight:bold
    classDef pool fill:#3498DB,stroke:#2980B9,color:#fff,font-weight:bold
    classDef reuse fill:#2ECC71,stroke:#27AE60,color:#fff,font-weight:bold
    classDef create fill:#F39C12,stroke:#E67E22,color:#fff,font-weight:bold

    class A remove
    class C destroy
    class D,E pool
    class F reuse
    class H create
    class I,J,K,L reuse

2.2 复用组件的生命周期

普通组件和@Reusable组件的生命周期差异:

生命周期回调 普通组件 @Reusable组件 说明
aboutToAppear 首次创建时调用 首次创建时调用 只在真正创建时触发
aboutToReuse 不存在 每次从复用池取出时调用 复用时重新初始化状态
aboutToRecycle 不存在 进入复用池前调用 清理状态、释放临时资源
onPageHide 页面隐藏时调用 页面隐藏时调用 两者一致
aboutToDisappear 销毁时调用 最终销毁时调用 复用池清空时才触发

2.3 复用池的大小与管理策略

flowchart LR
    A[组件进入复用池] --> B{池是否已满?}
    B -->|未满| C[直接入池]
    B -->|已满| D[销毁最早入池的组件]
    D --> E[新组件入池]

    classDef enter fill:#3498DB,stroke:#2980B9,color:#fff,font-weight:bold
    classDef check fill:#F39C12,stroke:#E67E22,color:#fff,font-weight:bold
    classDef action fill:#2ECC71,stroke:#27AE60,color:#fff,font-weight:bold
    classDef evict fill:#E74C3C,stroke:#C0392B,color:#fff,font-weight:bold

    class A enter
    class B check
    class C,E action
    class D evict

复用池的默认大小是有限的(通常与屏幕可显示的组件数量相关)。当池满时,采用FIFO(先进先出)策略淘汰最早入池的组件。开发者也可以通过配置调整池的大小。


三、代码实战

3.1 基础示例:@Reusable装饰器基本用法

// ✅ 基础用法:标记@Reusable,实现aboutToReuse和aboutToRecycle
@Reusable
@Component
struct ReusableChatBubble {
  @Prop message: string = ''
  @Prop isMine: boolean = false
  @Prop avatar: string = ''
  private timestamp: number = 0

  // 首次创建时调用
  aboutToAppear(): void {
    console.info('ChatBubble: 首次创建')
    this.timestamp = Date.now()
  }

  // 从复用池取出时调用 —— 重新初始化状态
  aboutToReuse(params: Record<string, Object>): void {
    console.info('ChatBubble: 从复用池取出,重新初始化')
    // 重新设置时间戳
    this.timestamp = Date.now()
    // 可以根据params做额外的初始化
    if (params['extraData']) {
      console.info(`收到额外数据: ${params['extraData']}`)
    }
  }

  // 进入复用池前调用 —— 清理临时状态
  aboutToRecycle(): void {
    console.info('ChatBubble: 进入复用池,清理状态')
    // 清理临时数据,避免下次复用时残留
    this.timestamp = 0
  }

  build() {
    Row() {
      if (!this.isMine) {
        // 对方消息:头像在左
        Image(this.avatar || $r('app.media.icon'))
          .width(36)
          .height(36)
          .borderRadius(18)
          .margin({ right: 8 })
      }

      // 消息气泡
      Column() {
        Text(this.message)
          .fontSize(15)
          .fontColor(this.isMine ? Color.White : Color.Black)
          .padding(12)
          .borderRadius(12)
          .backgroundColor(this.isMine ? '#07C160' : Color.White)
          .shadow({
            radius: 2,
            color: '#1A000000',
            offsetX: 0,
            offsetY: 1
          })
      }
      .alignItems(this.isMine ? HorizontalAlign.End : HorizontalAlign.Start)

      if (this.isMine) {
        // 我的消息:头像在右
        Image(this.avatar || $r('app.media.icon'))
          .width(36)
          .height(36)
          .borderRadius(18)
          .margin({ left: 8 })
      }
    }
    .width('100%')
    .padding({ left: 16, right: 16, top: 4, bottom: 4 })
    .justifyContent(this.isMine ? FlexAlign.End : FlexAlign.Start)
  }
}

3.2 进阶示例:复用池大小配置与调优

import { hiTraceMeter } from '@kit.PerformanceAnalysisKit'

// 复用统计信息
interface ReuseStats {
  createCount: number    // 创建次数
  reuseCount: number     // 复用次数
  recycleCount: number   // 回收次数
  hitRate: number        // 复用命中率
}

// 带统计的复用组件
@Reusable
@Component
struct ReusableProductCard {
  @Prop productName: string = ''
  @Prop price: number = 0
  @Prop imageUrl: string = ''
  @Prop rating: number = 0
  private static stats: ReuseStats = { createCount: 0, reuseCount: 0, recycleCount: 0, hitRate: 0 }

  aboutToAppear(): void {
    ReusableProductCard.stats.createCount++
    console.info(`[复用统计] 创建次数: ${ReusableProductCard.stats.createCount}`)
  }

  aboutToReuse(params: Record<string, Object>): void {
    ReusableProductCard.stats.reuseCount++
    const total = ReusableProductCard.stats.createCount + ReusableProductCard.stats.reuseCount
    ReusableProductCard.stats.hitRate = ReusableProductCard.stats.reuseCount / total
    console.info(`[复用统计] 复用次数: ${ReusableProductCard.stats.reuseCount}, 命中率: ${(ReusableProductCard.stats.hitRate * 100).toFixed(1)}%`)
  }

  aboutToRecycle(): void {
    ReusableProductCard.stats.recycleCount++
  }

  // 生成星级评分
  private getStarText(): string {
    const full = Math.floor(this.rating)
    const half = this.rating % 1 >= 0.5 ? 1 : 0
    return '★'.repeat(full) + (half ? '☆' : '') + ' '.repeat(5 - full - half)
  }

  build() {
    Column() {
      // 商品图片
      Image(this.imageUrl || $r('app.media.icon'))
        .width('100%')
        .height(120)
        .objectFit(ImageFit.Cover)
        .borderRadius({ topLeft: 8, topRight: 8 })
        .backgroundColor('#F5F5F5')

      // 商品信息
      Column() {
        Text(this.productName)
          .fontSize(14)
          .fontWeight(FontWeight.Medium)
          .maxLines(2)
          .textOverflow({ overflow: TextOverflow.Ellipsis })

        Row() {
          Text(`¥${this.price.toFixed(2)}`)
            .fontSize(16)
            .fontColor(Color.Red)
            .fontWeight(FontWeight.Bold)
          Text(this.getStarText())
            .fontSize(12)
            .fontColor('#FFB800')
            .margin({ left: 8 })
        }
        .margin({ top: 6 })
      }
      .padding(8)
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .backgroundColor(Color.White)
    .borderRadius(8)
    .shadow({ radius: 4, color: '#0D000000', offsetX: 0, offsetY: 2 })
  }
}

@Entry
@Component
struct ReusePoolConfigDemo {
  @State products: Array<Record<string, Object>> = Array.from({ length: 50 }, (_, i) => ({
    name: `商品 ${i + 1}`,
    price: Math.random() * 500 + 10,
    rating: Math.random() * 2 + 3,
    image: ''
  }))

  build() {
    Column() {
      Text('组件复用池配置示例')
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 16 })

      // 网格列表
      Grid() {
        ForEach(this.products, (product: Record<string, Object>, index?: number) => {
          GridItem() {
            ReusableProductCard({
              productName: product.name as string,
              price: product.price as number,
              imageUrl: product.image as string,
              rating: product.rating as number
            })
          }
        }, (product: Record<string, Object>, index?: number) => `product_${index}`)
      }
      .columnsTemplate('1fr 1fr')
      .rowsGap(12)
      .columnsGap(12)
      .width('100%')
      .layoutWeight(1)
      .cachedCount(4)  // 预缓存4行,增加复用命中率

      // 操作按钮
      Row() {
        Button('刷新数据')
          .layoutWeight(1)
          .onClick(() => {
            this.products = Array.from({ length: 50 }, (_, i) => ({
              name: `新商品 ${i + 1}`,
              price: Math.random() * 500 + 10,
              rating: Math.random() * 2 + 3,
              image: ''
            }))
          })
      }
      .width('100%')
      .margin({ top: 16 })
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .backgroundColor('#F5F5F5')
  }
}

3.3 完整示例:列表项复用实战

一个完整的聊天列表场景,展示@Reusable在实际项目中的综合运用:

interface ChatMessage {
  id: string
  content: string
  sender: string
  isMine: boolean
  timestamp: number
  type: 'text' | 'image' | 'system'
}

// 复用聊天消息组件
@Reusable
@Component
struct ReusableMessageItem {
  @Prop message: ChatMessage | null = null
  private loadStartTime: number = 0

  aboutToAppear(): void {
    this.loadStartTime = Date.now()
  }

  aboutToReuse(params: Record<string, Object>): void {
    // 复用时重置计时
    this.loadStartTime = Date.now()
    console.info(`[复用] 消息组件复用,节省创建时间约${this.loadStartTime}ms`)
  }

  aboutToRecycle(): void {
    // 清理状态
    this.loadStartTime = 0
  }

  // 格式化时间
  private formatTime(ts: number): string {
    const date = new Date(ts)
    return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
  }

  build() {
    if (!this.message) {
      return
    }

    // 系统消息
    if (this.message.type === 'system') {
      Row() {
        Text(this.message.content)
          .fontSize(12)
          .fontColor(Color.Gray)
          .padding(4)
          .backgroundColor('#F0F0F0')
          .borderRadius(4)
      }
      .width('100%')
      .justifyContent(FlexAlign.Center)
      .padding({ top: 8, bottom: 8 })
      return
    }

    // 普通消息
    Row() {
      if (!this.message.isMine) {
        // 对方头像
        Image($r('app.media.icon'))
          .width(36)
          .height(36)
          .borderRadius(18)
          .margin({ right: 8 })
      }

      Column() {
        // 消息内容
        if (this.message.type === 'image') {
          Image($r('app.media.icon'))
            .width(160)
            .height(120)
            .borderRadius(8)
            .objectFit(ImageFit.Cover)
        } else {
          Text(this.message.content)
            .fontSize(15)
            .padding(10)
            .borderRadius(8)
            .fontColor(this.message.isMine ? Color.White : Color.Black)
            .backgroundColor(this.message.isMine ? '#07C160' : Color.White)
        }

        // 时间戳
        Text(this.formatTime(this.message.timestamp))
          .fontSize(10)
          .fontColor('#999999')
          .margin({ top: 4 })
      }
      .alignItems(this.message.isMine ? HorizontalAlign.End : HorizontalAlign.Start)

      if (this.message.isMine) {
        // 我的头像
        Image($r('app.media.icon'))
          .width(36)
          .height(36)
          .borderRadius(18)
          .margin({ left: 8 })
      }
    }
    .width('100%')
    .padding({ left: 16, right: 16, top: 4, bottom: 4 })
    .justifyContent(this.message.isMine ? FlexAlign.End : FlexAlign.Start)
  }
}

@Entry
@Component
struct ChatListReuseDemo {
  @State messages: ChatMessage[] = []

  aboutToAppear(): void {
    // 初始化聊天数据
    this.messages = this.generateMessages(100)
  }

  // 生成模拟聊天数据
  private generateMessages(count: number): ChatMessage[] {
    const msgs: ChatMessage[] = []
    const contents = ['你好!', '在吗?', '今天天气不错', '收到,谢谢!', '好的,没问题',
      '稍等一下', '我看看', '马上处理', '已确认', '周末见!']
    const senders = ['小明', '小红', '小刚', '我']

    for (let i = 0; i < count; i++) {
      const isSystem = i % 20 === 0
      const senderIndex = Math.floor(Math.random() * senders.length)
      const isMine = senders[senderIndex] === '我'

      msgs.push({
        id: `msg_${i}`,
        content: isSystem ? '--- 以下是新消息 ---' : contents[Math.floor(Math.random() * contents.length)],
        sender: isSystem ? 'system' : senders[senderIndex],
        isMine: isMine,
        timestamp: Date.now() - (count - i) * 60000,
        type: isSystem ? 'system' : (Math.random() > 0.9 ? 'image' : 'text')
      })
    }
    return msgs
  }

  build() {
    Column() {
      // 标题栏
      Row() {
        Text('聊天列表 - 组件复用实战')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        Blank()
        Text(`${this.messages.length}条消息`)
          .fontSize(14)
          .fontColor(Color.Gray)
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 12, bottom: 12 })
      .backgroundColor(Color.White)

      Divider().color('#E0E0E0')

      // 聊天列表 - 使用@Reusable组件
      List({ space: 0 }) {
        LazyForEach(
          new ChatDataSource(this.messages),
          (msg: ChatMessage) => {
            ListItem() {
              ReusableMessageItem({ message: msg })
            }
          },
          (msg: ChatMessage) => msg.id
        )
      }
      .width('100%')
      .layoutWeight(1)
      .cachedCount(5)

      // 底部输入栏
      Row() {
        TextInput({ placeholder: '输入消息...' })
          .layoutWeight(1)
          .height(40)
          .borderRadius(20)
          .backgroundColor('#F5F5F5')
          .padding({ left: 16, right: 16 })

        Button('发送')
          .height(40)
          .margin({ left: 8 })
          .onClick(() => {
            this.messages.push({
              id: `msg_${this.messages.length}`,
              content: '新发送的消息',
              sender: '我',
              isMine: true,
              timestamp: Date.now(),
              type: 'text'
            })
          })
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 8, bottom: 8 })
      .backgroundColor(Color.White)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#EDEDED')
  }
}

// 数据源类 - 配合LazyForEach使用
class ChatDataSource implements IDataSource {
  private messages: ChatMessage[] = []
  private listeners: DataChangeListener[] = []

  constructor(messages: ChatMessage[]) {
    this.messages = messages
  }

  totalCount(): number {
    return this.messages.length
  }

  getData(index: number): ChatMessage {
    return this.messages[index]
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      this.listeners.push(listener)
    }
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener)
    if (pos >= 0) {
      this.listeners.splice(pos, 1)
    }
  }

  // 通知数据变更
  notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataChange(index)
    })
  }

  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded()
    })
  }
}

四、踩坑与注意事项

坑点1:aboutToRecycle中忘记清理状态导致"脏数据"

组件进入复用池时,如果不清理上一次的状态,下次复用时可能显示上一次的残留数据:

@Reusable
@Component
struct ReusableCard {
  @Prop title: string = ''
  private tempData: string = ''  // 非响应式临时数据

  aboutToRecycle(): void {
    // ❌ 忘记清理tempData,下次复用可能残留
    // this.tempData = ''

    // ✅ 必须清理所有临时状态
    this.tempData = ''
  }

  aboutToReuse(params: Record<string, Object>): void {
    // ✅ 复用时重新初始化
    this.tempData = params['tempData'] as string || ''
  }
}

坑点2:@Reusable组件中使用了@StorageLink等全局状态

@Reusable组件被复用时,全局状态(如@StorageLink、@StorageProp)不会自动重置。如果组件依赖全局状态做差异化显示,复用后可能出现数据错乱:

@Reusable
@Component
struct ReusableGlobalStateCard {
  // ❌ 危险:全局状态在复用时不会重置
  @StorageLink('currentTheme') theme: string = 'light'

  aboutToReuse(params: Record<string, Object>): void {
    // ✅ 复用时手动同步全局状态
    // 注意:AppStorage中的值可能已经变化
    this.theme = AppStorage.get<string>('currentTheme') || 'light'
  }
}

坑点3:aboutToReuse中执行耗时操作

aboutToReuse在组件重新加入组件树之前调用,如果在这里执行耗时操作,会阻塞渲染:

@Reusable
@Component
struct ReusableHeavyCard {
  @Prop dataId: string = ''
  private detailData: Record<string, Object> | null = null

  aboutToReuse(params: Record<string, Object>): void {
    // ❌ 在aboutToReuse中执行耗时加载
    // this.detailData = this.loadDetailFromDisk(params['dataId'] as string)

    // ✅ 先设置基本状态,异步加载详情
    this.dataId = params['dataId'] as string
    this.loadDetailAsync(this.dataId)
  }

  private async loadDetailAsync(id: string): Promise<void> {
    // 异步加载,不阻塞渲染
    // const result = await this.loadDetailFromDisk(id)
    // this.detailData = result
  }
}

坑点4:@Reusable与ForEach配合无效

ForEach在数据变更时可能会销毁并重建组件,而不是复用。要让@Reusable真正发挥作用,需要配合LazyForEach使用:

// ❌ ForEach可能绕过复用池
ForEach(this.dataList, (item) => {
  ReusableCard({ title: item.name })
}, (item) => item.id)

// ✅ LazyForEach配合@Reusable,复用效果最佳
LazyForEach(this.dataSource, (item) => {
  ReusableCard({ title: item.name })
}, (item) => item.id)

坑点5:复用池大小不足导致频繁创建销毁

默认的复用池大小可能不够用,特别是在快速滑动列表时。如果复用命中率低,需要通过cachedCount增加预缓存数量:

// ⚠️ 默认cachedCount可能不够
List() {
  LazyForEach(this.dataSource, (item) => {
    ListItem() {
      ReusableCard({ title: item.name })
    }
  }, (item) => item.id)
}
// ✅ 根据列表项高度和屏幕尺寸调整cachedCount
.cachedCount(8)  // 预缓存8个列表项

坑点6:@Reusable组件的子组件不会自动复用

@Reusable只作用于被标记的组件本身,其子组件不会自动获得复用能力。如果子组件也是高频创建销毁的,需要单独标记@Reusable:

@Reusable
@Component
struct ReusableParent {
  build() {
    Column() {
      // ❌ ChildComponent不会被自动复用
      // ChildComponent({ data: this.data })

      // ✅ 子组件也标记@Reusable
      ReusableChild({ data: this.data })
    }
  }
}

@Reusable
@Component
struct ReusableChild {
  @Prop data: string = ''

  aboutToReuse(params: Record<string, Object>): void {
    this.data = params['data'] as string || ''
  }

  build() {
    Text(this.data)
  }
}

五、HarmonyOS 6适配说明

API差异表

API/特性 HarmonyOS 5 HarmonyOS 6 变更说明
@Reusable 基础复用功能 支持复用池大小配置 可自定义每种组件的池大小
aboutToReuse 无参数类型约束 强类型params 参数类型更安全
复用池监控 无官方API getReusePoolStats() 可查询复用池状态
复用优先级 FIFO 支持LRU策略 更智能的淘汰策略
复用跨页面 不支持 支持跨页面复用 同类型组件可跨页面共享复用池

行为变更

  1. 复用池默认大小调整:HarmonyOS 6根据设备内存大小动态调整复用池默认容量,高端设备池更大
  2. aboutToReuse参数增强:params参数现在支持嵌套对象和数组类型
  3. 复用池自动扩容:当复用命中率低于50%时,框架会自动扩容复用池

适配代码

import { ComponentUtils } from '@kit.ArkUI'

@Reusable
@Component
struct HarmonyOS6ReusableCard {
  @Prop title: string = ''
  @Prop subtitle: string = ''
  private reuseCount: number = 0

  aboutToAppear(): void {
    console.info('[ReusableCard] 首次创建')
  }

  aboutToReuse(params: Record<string, Object>): void {
    this.reuseCount++
    console.info(`[ReusableCard] 第${this.reuseCount}次复用`)

    // HarmonyOS 6: 支持嵌套参数
    if (params['config']) {
      const config = params['config'] as Record<string, Object>
      console.info(`配置项: ${JSON.stringify(config)}`)
    }
  }

  aboutToRecycle(): void {
    console.info('[ReusableCard] 进入复用池')
  }

  build() {
    Column() {
      Text(this.title)
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
      Text(this.subtitle)
        .fontSize(13)
        .fontColor(Color.Gray)
        .margin({ top: 4 })
    }
    .width('100%')
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(12)
  }
}

@Entry
@Component
struct HarmonyOS6ReuseAdaptation {
  @State dataList: Array<Record<string, Object>> = Array.from({ length: 50 }, (_, i) => ({
    title: `标题 ${i}`,
    subtitle: `副标题 ${i}`,
    config: { theme: i % 2 === 0 ? 'light' : 'dark' }
  }))

  aboutToAppear(): void {
    // HarmonyOS 6: 查询复用池状态(降级处理)
    try {
      // const stats = ComponentUtils.getReusePoolStats('HarmonyOS6ReusableCard')
      // console.info(`复用池状态: ${JSON.stringify(stats)}`)
      console.info('复用池状态查询可用')
    } catch (e) {
      console.warn('复用池状态查询不可用,降级处理')
    }
  }

  build() {
    Column() {
      Text('HarmonyOS 6 组件复用适配')
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 16 })

      List({ space: 8 }) {
        LazyForEach(
          new SimpleDataSource(this.dataList),
          (item: Record<string, Object>) => {
            ListItem() {
              HarmonyOS6ReusableCard({
                title: item.title as string,
                subtitle: item.subtitle as string
              })
            }
          },
          (item: Record<string, Object>, index?: number) => `item_${index}`
        )
      }
      .width('100%')
      .layoutWeight(1)
      .cachedCount(6)
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .backgroundColor('#F5F5F5')
  }
}

// 简易数据源
class SimpleDataSource implements IDataSource {
  private data: Array<Record<string, Object>> = []
  private listeners: DataChangeListener[] = []

  constructor(data: Array<Record<string, Object>>) {
    this.data = data
  }

  totalCount(): number {
    return this.data.length
  }

  getData(index: number): Record<string, Object> {
    return this.data[index]
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      this.listeners.push(listener)
    }
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener)
    if (pos >= 0) {
      this.listeners.splice(pos, 1)
    }
  }
}

六、总结

三维度评价表

维度 评分 说明
理论深度 ⭐⭐⭐⭐ 复用池机制设计精巧,生命周期管理是核心难点
实战价值 ⭐⭐⭐⭐⭐ 列表场景下效果立竿见影,复用命中率可从0%提升到80%+
上手难度 ⭐⭐⭐ @Reusable用法简单,但aboutToRecycle清理和aboutToReuse初始化容易遗漏

组件复用池的本质思想其实很简单:别扔,留着下次用。但魔鬼藏在细节里——什么时候清理?什么时候初始化?怎么和LazyForEach配合?这些都是实战中必须掌握的。

我的建议是:凡是列表项组件,一律加上@Reusable。这是成本最低、收益最高的优化手段之一。加上之后,记得实现aboutToRecycle做清理、aboutToReuse做初始化,配合LazyForEachcachedCount,你的列表滑动体验会有质的飞跃。

记住:创建一个组件的成本远高于复用一个组件。就像招聘新员工和召回老员工的区别——前者需要走完整的招聘流程,后者只需要说一句"回来上班吧"。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。