项目 Git 地址:https://gitcode.com/HarmonyOS_Samples/component-reuse

真实的信息流,很少所有卡片都长一样。

有的只有文字,有的右边带一张图,有的直接三张图横排。这个时候还想做组件复用,就不能只靠一个 ItemView 走天下了。

A hand-drawn sketch-note illustration of a mobile

项目里的 MultiTypeItemPage.ets 正好讲这个场景。

页面效果先看一眼

多类型列表复用

这个页面里混着不同样式的列表项。看起来像普通新闻流,但代码里已经按类型拆好了组件。

数据用 type 区分结构

模拟数据里,每条 ItemData 都有一个 type

export class ItemData {
  id: string = '';
  type: number = 0;
  pics: Resource[] = [];
  preview: Resource | string = '';
}

MockData.ets 里生成数据时这样写:

const data = new ItemData('' + i, i % 3);

也就是说,type 会在 0、1、2 之间轮换。

这三个值刚好对应三种卡片结构。

LazyForEach 里按类型选择组件

核心代码在这里:

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())

这段逻辑很直白:

  • type === 0:纯文本卡片。
  • type === 1:右侧单图卡片。
  • type === 2:三图卡片。

你可以把它理解成一个轻量版的列表项工厂。

三个组件都加了 @Reusable

项目没有把三个结构塞进一个组件里硬判断,而是拆成三个组件:

@Reusable
@Component
struct TextTypeItemView {
  @ObjectLink item: ItemData;
}

@Reusable
@Component
struct ImageTypeItemView {
  @ObjectLink item: ItemData;
}

@Reusable
@Component
struct ThreeImageTypeItemView {
  @ObjectLink item: ItemData;
}

这个拆法我比较推荐。

如果全塞进一个大组件,代码短期看着少,后面状态和布局会越来越乱。按结构拆开,复用池也更清楚。

为什么每个类型要独立 reuseId

这点非常重要。

项目分别用了:

.reuseId('text_item_id')
.reuseId('image_item_id')
.reuseId('three_image_item_id')

因为三种卡片结构不同。

纯文本卡片没有图片区域,单图卡片右侧有图片,三图卡片中间有图片 Row。如果它们共用一个 reuseId,框架可能拿一个旧的单图组件去显示纯文本数据,风险就来了。

复用的前提是结构能匹配。

这种拆法适合业务扩展

假设后面加一个视频卡片,你只需要:

else if (item.type === 3) {
  VideoTypeItemView({ item: item })
    .reuseId('video_item_id')
}

然后新增一个 VideoTypeItemView

旧的三种组件不用动太多,复用池也不会被污染。

小白别急着优化 if else

有人会觉得 if else 不够优雅,想上来就写映射表、工厂类。

我建议先别急。

在 ArkUI 页面里,结构分支本来就很常见。等类型真的多到维护困难,再抽象也不迟。这个项目现在这样写,反而更适合学习。

看看三种卡片到底差在哪

纯文本卡片只需要标题、来源和时间:

@Reusable
@Component
struct TextTypeItemView {
  @ObjectLink item: ItemData;

  build() {
    Column() {
      Text(this.item.title)
        .maxLines(3)
      Row() {
        Text(this.item.from)
        Text(this.item.tail)
      }
    }
  }
}

单图卡片多了右侧图片:

@Reusable
@Component
struct ImageTypeItemView {
  @ObjectLink item: ItemData;

  build() {
    Row() {
      Column() {
        Text(this.item.title)
        Text(this.item.tail)
      }
      .layoutWeight(1)

      Image(this.item.preview)
        .width(96)
        .height(78)
        .borderRadius(8)
    }
  }
}

三图卡片中间是三张图:

@Reusable
@Component
struct ThreeImageTypeItemView {
  @ObjectLink item: ItemData;

  build() {
    Column() {
      Text(this.item.title)
      Row() {
        Image(this.item.pics[0]).layoutWeight(1)
        Image(this.item.pics[1]).layoutWeight(1)
        Image(this.item.pics[2]).layoutWeight(1)
      }
      Text(this.item.tail)
    }
  }
}

把这三段放在一起,你会发现它们不是“数据不同”,而是“结构不同”。结构不同,就要拆组件,也要拆 reuseId

新增一种卡片该怎么做

真实业务里经常会加新类型,比如视频卡片。照这个项目的思路,你要做三件事:

// 1. 数据里约定 type === 3 表示视频卡片
else if (item.type === 3) {
  VideoTypeItemView({ item: item })
    .reuseId('video_item_id')
}
// 2. 新建视频卡片组件
@Reusable
@Component
struct VideoTypeItemView {
  @ObjectLink item: ItemData;

  build() {
    Stack({ alignContent: Alignment.BottomEnd }) {
      Image(this.item.preview)
      Text(this.item.duration)
    }
  }
}

第三件事最容易忘:确认 video_item_id 不要和旧类型混用。

写在最后

多类型列表复用的核心不是“怎么少写代码”,而是“怎么把不同结构分清楚”。

组件拆清楚,reuseId 拆清楚,复用就稳很多。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐