HarmonyOS鸿蒙Next中想实现组件复用的能力

HarmonyOS鸿蒙Next中想实现组件复用的能力 【问题描述】:长列表 中使用 @Resuable 组件复用,按照官网案例写的,但是使用profiler看的时候还是提示组件复用未生效 官方文档 : https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-component-reuse#section43301824133220

官方示例代码仓库:https://gitee.com/harmonyos_samples/component-reuse/

【问题现象】:自己改写代码在附件文件中


更多关于HarmonyOS鸿蒙Next中想实现组件复用的能力的实战教程也可以访问 https://www.itying.com/category-93-b0.html

4 回复

开发者你好,

组件复用是生效的,使用@ObjectLink接收aboutToReuse传递过来的参数会导致报错,需要把@ObjectLink修改为@State接收来自aboutToReuse的参数,参考列表滚动配合lazyforeach使用,修改后的MultiTypeItemPage.ets文件如下:

/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ItemData } from '../model/ItemData';
import { genMockItemData } from '../common/MockData';
import { ItemDataSource } from '../model/ItemDataSource';

// [Start MultiType]
@Component
export struct MultiTypeItemPage {
  // [StartExclude MultiType]
  private dataSource: ItemDataSource = new ItemDataSource();

  aboutToAppear(): void {
    this.dataSource.pushArray(genMockItemData(1000));
  }
  // [EndExclude MultiType]

  build() {
    NavDestination() {
      Column() {
        List() {
          LazyForEach(this.dataSource, (item: ItemData) => {
            if (item.type === 0) {
              TextTypeItemView({ item: item })
                .reuseId('text_item_id')
            } else if (item.type === 1) {
              ImageTypeItemView({ item: item })
                .reuseId('image_item_id')
            } else if (item.type === 2) {
              ThreeImageTypeItemView({ item: item })
                .reuseId('three_image_item_id')
            }
          }, (item: ItemData) => item.id.toString())
        }
        // [StartExclude MultiType]
        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
        .cachedCount(1)
        .width('100%')
        .height('100%')
        // [EndExclude MultiType]
      }
      // [StartExclude MultiType]
      .width('100%')
      .height('100%')
      // [EndExclude MultiType]
    }
    // [StartExclude MultiType]
    .backgroundColor(0xF1F3F5)
    .title($r('app.string.index_same_list_multi_type'))
    // [EndExclude MultiType]
  }
}

@Reusable
@Component
struct TextTypeItemView {
  // [StartExclude MultiType]
  @State item: ItemData = new ItemData('1',1);

  aboutToReuse(params: Record<string, Object | null | undefined>): void {
    this.item = params.item as ItemData
    console.log('组件复用')
  }
  build() {
    Column() {
      Text(this.item.title)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .fontColor(Color.Black)
        .maxLines(3)
        .lineHeight(22)
        .opacity(0.9)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
        .width('100%')
      Row() {
        Text(this.item.from)
          .fontSize(12)
          .fontWeight(FontWeight.Regular)
          .fontColor(0x0A59F7)
        Text(this.item.tail)
          .fontSize(12)
          .opacity(0.4)
          .fontWeight(FontWeight.Regular)
          .margin({ left: 6 })
          .width('100%')
      }
      .margin({ top: 12 })
    }
    .padding({
      top: 16,
      bottom: 12,
      left: 16,
      right: 16
    })
    .margin({ top: 12, left: 16, right: 16 })
    .borderRadius(12)
    .backgroundColor(Color.White)
  }
  // [EndExclude MultiType]
}


@Reusable
@Component
struct ImageTypeItemView {
  // [StartExclude MultiType]
  @ObjectLink item: ItemData;

  build() {
    Row() {
      Column() {
        Text(this.item.title)
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .fontColor(Color.Black)
          .maxLines(2)
          .lineHeight(22)
          .opacity(0.9)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
        Text(this.item.tail)
          .fontSize(12)
          .opacity(0.4)
          .fontWeight(FontWeight.Regular)
          .margin({ top: 18 })
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)

      Image(this.item.preview)
        .width(96)
        .height(78)
        .borderRadius(8)
        .margin({ left: 12 })
    }
    .alignItems(VerticalAlign.Top)
    .padding({
      top: 16,
      bottom: 12,
      left: 16,
      right: 16
    })
    .margin({ top: 12, left: 16, right: 16 })
    .borderRadius(12)
    .backgroundColor(Color.White)
  }
  // [EndExclude MultiType]
}

@Reusable
@Component
struct ThreeImageTypeItemView {
  // [StartExclude MultiType]
  @ObjectLink item: ItemData;

  build() {
    Column() {
      Text(this.item.title)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .fontColor(Color.Black)
        .maxLines(2)
        .lineHeight(22)
        .opacity(0.9)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
        .width('100%')
      Row() {
        Image(this.item.pics[0])
          .layoutWeight(1)
          .height(98)
          .borderRadius({ topLeft: 8, bottomLeft: 8 })
        Divider().width(2)
        Image(this.item.pics[1])
          .layoutWeight(1)
          .height(98)
        Divider().width(2)
        Image(this.item.pics[2])
          .layoutWeight(1)
          .height(98)
          .borderRadius({ topRight: 8, bottomRight: 8 })
      }
      .margin({ top: 8 })

      Text(this.item.tail)
        .fontSize(12)
        .opacity(0.4)
        .fontWeight(FontWeight.Regular)
        .margin({ top: 18 })
        .margin({ top: 8 })
    }
    .alignItems(HorizontalAlign.Start)
    .padding({
      top: 16,
      bottom: 12,
      left: 16,
      right: 16
    })
    .margin({ top: 12, left: 16, right: 16 })
    .borderRadius(12)
    .backgroundColor(Color.White)
  }
  // [EndExclude MultiType]
}
// [End MultiType]

更多关于HarmonyOS鸿蒙Next中想实现组件复用的能力的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


从提供的代码上看,可以从以下方面进行检查调整

在 LazyForEach 中使用 @Reusable 组件时必须通过 .reuseId() 方法显式设置唯一标识符。 代码问题:当前示例中 reuseId 仅作为注释存在(//TODO reuseId),实际未添加 .reuseId() 方法调用。 复用组件时需通过 aboutToReuse 方法更新数据,但若参数传递不完整或类型不匹配,会导致复用失败。 增加 cachedCount 提升复用机会

HarmonyOS Next中组件复用主要通过ArkUI的@Builder@BuilderParam@Extend装饰器实现。@Builder用于定义可复用的UI片段;@BuilderParam允许组件接收@Builder作为参数,实现动态UI组合;@Extend用于扩展组件样式,实现样式复用。此外,自定义组件是核心复用单元,通过封装状态、样式和逻辑来构建独立功能模块。

在HarmonyOS Next中,@Reusable 装饰器用于标记组件具备可复用能力,但实际复用效果需要满足特定条件。根据你的描述,Profiler提示复用未生效,通常有以下原因:

  1. 组件状态未满足复用条件@Reusable 组件仅在组件树结构相同且组件状态(状态变量、属性)一致时才会被复用。如果每次渲染时状态或属性发生变化,复用将不会触发。

  2. 父组件未使用复用优化:确保父组件(如长列表的 ListItem)使用了 if/elseForEach 等动态渲染逻辑,且子组件被正确标记为 @Reusable

  3. 组件结构不一致:检查复用的组件是否在每次渲染时保持相同的组件结构(如相同的子组件数量和类型)。

建议检查:

  • 确保 @Reusable 组件内部状态稳定,避免每次渲染都生成新状态。
  • 使用 Profiler 工具观察组件更新时的差异,确认是否有属性变化导致复用失效。
  • 参考官方示例,对比代码中 @Reusable 组件的使用方式是否一致。
回到顶部