在这里插入图片描述
在这里插入图片描述

一、引言

在大型应用中,UI 结构往往存在大量重复。如果每个页面都从零编写 UI,不仅代码冗余,而且难以维护。HarmonyOS ArkUI 提供了强大的组件复用机制,包括 @Component 自定义组件、@Builder 构建函数、@BuilderParam 插槽、@Styles 样式复用、@Extend 扩展样式等。

本文将以一个圆润可爱的紫粉风格页面为主线,深入讲解 ArkUI 组件复用的各种机制,通过大量代码示例帮助读者掌握"一次编写、多处复用"的精髓。

二、组件复用的层次

ArkUI 的组件复用可以从多个层次进行:

复用层次机制说明
结构复用@Component封装完整组件,含状态和逻辑
片段复用@Builder复用一段 UI 结构
插槽复用@BuilderParam自定义组件的内容插槽
样式复用@Styles复用通用样式
扩展复用@Extend扩展系统组件样式
性能复用@Reusable列表滚动时的组件复用

三、@Component 自定义组件

3.1 基本概念

@Component 装饰的结构体就是一个自定义组件。它由两部分组成:状态数据(@State、@Prop 等)和 build 方法(定义 UI 结构)。

@Component
struct MyComponent {
  // 状态数据
  @Prop title: string = '';
  @State count: number = 0;

  // UI 结构
  build() {
    Column() {
      Text(this.title)
      Text(`${this.count}`)
    }
  }
}

3.2 组件的生命周期

自定义组件有完整的生命周期方法:

@Component
struct LifecycleDemo {
  aboutToAppear(): void {
    // 组件即将显示,适合初始化数据
    console.info('组件即将出现');
  }

  aboutToDisappear(): void {
    // 组件即将销毁,适合释放资源
    console.info('组件即将消失');
  }

  onPageShow(): void {
    // 页面显示时触发
  }

  onPageHide(): void {
    // 页面隐藏时触发
  }
}

四、@Builder 构建函数

@Builder 是 ArkUI 中最灵活的 UI 复用机制,它允许我们将一段 UI 结构封装为函数,在多个地方调用。

4.1 基本用法

@Builder
MyTitle(title: string) {
  Text(title)
    .fontSize(18)
    .fontWeight(FontWeight.Bold)
}

4.2 在 build 中调用

build() {
  Column() {
    this.MyTitle('标题一')
    this.MyTitle('标题二')
    this.MyTitle('标题三')
  }
}

4.3 传参的两种方式

方式一:按值传递

@Builder
Card(title: string, color: string) {
  Column() {
    Text(title)
  }
  .backgroundColor(color)
}

方式二:按引用传递(推荐)

@Builder
Card(item: { title: string, color: string }) {
  Column() {
    Text(item.title)
  }
  .backgroundColor(item.color)
}

按引用传递时,当传入对象的属性变化时,@Builder 内的 UI 会同步刷新,这是按值传递不具备的能力。

五、实战代码:紫粉可爱风自定义组件页面

下面我们实现一个演示组件复用的页面,页面采用圆润可爱的紫粉渐变风格。

5.1 定义数据结构

interface PropRow {
  prop: string;
  value: string;
}

代码说明:

PropRow 接口描述组件复用机制对照表中的一行数据,包含机制名称和说明文字。

5.2 定义可复用组件

// 自定义组件:@Builder 复用块
@Component
struct PillCard {
  @Prop title: string = '';
  @Prop color: string = '#A55EEA';

  build() {
    Column({ space: 6 }) {
      Text(this.title)
        .fontSize(14)
        .fontWeight(FontWeight.Bold)
        .fontColor(Color.White)
      Text('@Builder 复用卡片')
        .fontSize(10)
        .fontColor('#FFFFFFCC')
    }
    .width(150)
    .height(90)
    .justifyContent(FlexAlign.Center)
    .backgroundColor(this.color)
    .borderRadius(20)
    .shadow({ radius: 10, color: '#33A55EEA', offsetY: 4 })
  }
}

代码说明:

PillCard 是一个可复用的卡片组件:

  1. @Prop 参数titlecolor 两个属性,由父组件传入。color 控制卡片背景色,让同一个组件可以呈现不同颜色。

  2. @Prop 默认值@Prop title: string = '' 设置了默认值,即使父组件不传参也不会报错。

  3. UI 结构:卡片包含标题和说明文字,居中排列。

  4. 圆润风格.borderRadius(20) 大圆角、.shadow 柔和阴影,体现"圆润可爱"的设计风格。

  5. 复用价值:这个组件可以在任何页面中使用,只需传入不同的 titlecolor,就能生成不同样式的卡片,无需重复编写 UI 代码。

5.3 定义主页面

@Entry
@Component
struct CustomComponentPage {
  @State props: PropRow[] = [
    { prop: '@Component', value: '声明一个自定义组件' },
    { prop: '@Builder', value: '复用一段 UI 结构' },
    { prop: '@BuilderParam', value: '自定义组件插槽' },
    { prop: '@Styles', value: '复用通用样式' },
    { prop: '@Extend', value: '扩展系统组件样式' },
    { prop: '@Reusable', value: '组件复用优化性能' }
  ];
  @State colors: string[] = ['#A55EEA', '#FF6B9D', '#7ED6DF', '#FFEAA7', '#82CCDD', '#F8A5C2'];

代码说明:

  • @State props:组件复用机制对照表数据。
  • @State colors:一组颜色值,用于动态生成不同颜色的卡片,演示组件的参数化复用。

5.4 构建 UI

build() {
  Scroll() {
    Column({ space: 16 }) {
      // 顶部紫粉渐变
      Column() {
        Text('COMPONENT')
          .fontSize(12)
          .fontColor('#FFE0F0')
          .letterSpacing(4)
        Text('自定义组件')
          .fontSize(26)
          .fontWeight(FontWeight.Bold)
          .fontColor(Color.White)
          .margin({ top: 6 })
        Text('@Builder / @Component 复用之美')
          .fontSize(12)
          .fontColor('#FFE0F0')
          .margin({ top: 6 })
      }
      .width('100%')
      .padding({ top: 48, bottom: 30 })
      .linearGradient({
        angle: 160,
        colors: [['#A55EEA', 0], ['#FF6B9D', 1]]
      })

代码说明:

顶部标题区使用紫粉渐变(从紫色 #A55EEA 到粉色 #FF6B9D),angle: 160 表示渐变方向。粉色文字配合白色主标题,形成可爱风格。

      // Flex 弹性换行区
      Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceAround, alignItems: ItemAlign.Center }) {
        ForEach(this.colors, (c: string, i: number) => {
          PillCard({ title: `卡片 ${i + 1}`, color: c })
            .margin({ bottom: 12 })
        })
      }
      .width('100%')

代码说明:

这是组件复用的核心演示:

  1. Flex 弹性布局Flex({ wrap: FlexWrap.Wrap }) 设置自动换行,justifyContent: FlexAlign.SpaceAround 让卡片均匀分布。

  2. 循环创建组件ForEach 遍历 colors 数组,每次迭代创建同一个 PillCard 组件,但传入不同的 color

  3. 一次定义、多次复用PillCard 组件只定义了一次,却生成了 6 张不同颜色的卡片。这就是组件复用的价值——一份代码,多种形态

  4. 关键理解:组件复用的核心是"参数化"。通过将可变部分(颜色、标题、内容)抽象为参数,同一个组件就能适应多种场景。

      // 圆形彩色按钮组
      Row({ space: 18 }) {
        ForEach(this.colors, (c: string, i: number) => {
          Button(`${i + 1}`)
            .width(52)
            .height(52)
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor(Color.White)
            .backgroundColor(c)
            .borderRadius(26)
            .shadow({ radius: 8, color: '#33A55EEA', offsetY: 3 })
            .onClick(() => {
              this.props = this.props.slice(0, 6);
            })
        })
      }
      .width('100%')
      .justifyContent(FlexAlign.Center)
      .padding({ top: 8, bottom: 8 })

代码说明:

圆形彩色按钮组:

  • 使用 ForEach 循环创建 6 个圆形按钮,每个按钮颜色不同。
  • .borderRadius(26)(52 的一半)将按钮变成正圆。
  • 这种"同构异色"的设计风格与卡片区呼应,体现了页面的整体设计语言。
      // 属性表格:圆角标签行
      Column({ space: 8 }) {
        Text('组件复用机制速查')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .fontColor('#7B2FBE')
          .alignSelf(ItemAlign.Start)
        ForEach(this.props, (row: PropRow) => {
          Row({ space: 12 }) {
            Text(row.prop)
              .fontSize(12)
              .fontWeight(FontWeight.Bold)
              .fontColor(Color.White)
              .padding({ left: 12, right: 12, top: 6, bottom: 6 })
              .backgroundColor('#A55EEA')
              .borderRadius(12)
            Text(row.value)
              .fontSize(12)
              .fontColor('#555555')
              .layoutWeight(1)
          }
          .width('100%')
          .padding(10)
          .backgroundColor('#FFFFFF')
          .borderRadius(14)
          .shadow({ radius: 4, color: '#11000000', offsetY: 2 })
        })
      }
      .width('100%')
      .padding(14)
      .backgroundColor('#F7EFFC')
      .borderRadius(20)

代码说明:

组件复用机制速查表采用"圆角标签行"样式,区别于传统的表格:

  • 每行左侧是紫色圆角标签(机制名称),右侧是说明文字。
  • 每行是一个白色圆角卡片,带轻微阴影,形成"标签列表"效果。
  • 这种设计既有表格的信息展示功能,又比传统表格更活泼可爱,符合页面整体风格。

六、@BuilderParam 插槽机制

@BuilderParam 允许自定义组件接收外部传入的 UI 内容,类似 Vue 的插槽(slot)概念:

@Component
struct CardContainer {
  @BuilderParam content: () => void;

  build() {
    Column() {
      Text('卡片容器')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
      // 渲染外部传入的内容
      this.content()
    }
    .padding(16)
    .backgroundColor('#F5F5F5')
    .borderRadius(12)
  }
}

// 使用
@Component
struct Parent {
  @Builder
  customContent() {
    Row() {
      Text('这是自定义内容')
      Button('按钮')
    }
  }

  build() {
    CardContainer({ content: this.customContent })
  }
}

代码说明:

  • @BuilderParam content: () => void 声明一个插槽,类型是"无参无返回值的函数"。
  • 在组件内部通过 this.content() 调用,渲染外部传入的 UI。
  • 父组件通过 CardContainer({ content: this.customContent }) 传入自定义的 @Builder 函数。
  • 这种机制让组件更加灵活,同一个容器组件可以承载不同的内容。

七、@Styles 样式复用

@Styles 用于封装一组通用样式,避免重复书写相同的样式链:

// 全局样式
@Styles
function globalCardStyle() {
  .backgroundColor('#FFFFFF')
  .borderRadius(12)
  .shadow({ radius: 8, color: '#22000000', offsetY: 4 })
  .padding(16)
}

// 组件内样式
@Component
struct Demo {
  @Styles
  cardStyle() {
    .backgroundColor('#FFFFFF')
    .borderRadius(12)
  }

  build() {
    Column() {
      Text('卡片一').cardStyle()
      Text('卡片二').cardStyle()
    }
  }
}

代码说明:

  • @Styles 可以定义在组件外部(全局)或组件内部(局部)。
  • 全局 @Styles 函数名使用驼峰命名,组件内 @Styles 方法名首字母小写。
  • 通过 .cardStyle() 链式调用复用样式。
  • @Styles 只能包含通用属性(颜色、尺寸、边框等),不能包含事件回调。

八、@Extend 扩展组件样式

@Extend 用于扩展特定组件的样式,可以包含该组件特有的属性和事件:

@Extend(Text)
function priceText() {
  .fontSize(20)
  .fontWeight(FontWeight.Bold)
  .fontColor('#FF6348')
  .onClick(() => {
    console.info('价格被点击');
  })
}

// 使用
Text('¥99').priceText()

代码说明:

  • @Extend(Text) 表示扩展 Text 组件的样式。
  • @Styles 不同,@Extend 可以包含事件回调(如 onClick)。
  • @Extend 只能用于特定组件,而 @Styles 可以用于所有组件。

九、@Reusable 组件复用优化

在长列表滚动场景中,频繁创建和销毁组件会导致性能问题。@Reusable 允许组件在滚动时被复用:

@Reusable
@Component
struct ListItemView {
  @State item: string = '';

  aboutToReuse(params: Record<string, Object>): void {
    // 组件被复用时更新数据
    this.item = params.item as string;
  }

  build() {
    Row() {
      Text(this.item)
    }
    .height(60)
  }
}

代码说明:

  • @Reusable 标记组件可复用。
  • aboutToReuse 方法在组件被复用时调用,用于更新数据。
  • List 中使用 ListItem 包裹 @Reusable 组件,滚动时框架会自动复用,大幅提升性能。

十、组件复用最佳实践

10.1 组件拆分原则

  • 单一职责:一个组件只做一件事。
  • 参数化设计:将可变部分抽象为参数。
  • 合理粒度:避免组件过大或过小。

10.2 状态管理边界

自定义组件内部使用 @State 管理私有状态,通过 @Prop/@Link 与父组件通信,保持数据流清晰。

10.3 命名规范

  • 组件名使用大驼峰命名(如 PillCard)。
  • @Builder 方法名使用小驼峰(如 cardStyle)。
  • 全局 @Styles 函数使用大驼峰。

10.4 复用与性能

  • 列表场景使用 @Reusable 提升滚动性能。
  • 避免在 @Builder 中创建过多嵌套层级。
  • 合理使用 ForEach 的键生成器优化列表复用。

十一、常见问题

11.1 @Builder 传参不刷新

原因:按值传递的参数变化不会触发 @Builder 内 UI 刷新。

解决:改用按引用传递(对象参数)。

11.2 @BuilderParam 调用报错

原因:没有正确传入 @Builder 函数,或函数签名不匹配。

解决:确保传入的函数是 @Builder 装饰的,且签名一致。

11.3 @Styles 与 @Extend 混用报错

原因@Styles 不能包含事件,@Extend 只能用于特定组件。

解决:根据需求选择合适的机制。

十二、总结

本文深入讲解了 HarmonyOS 自定义组件的各种复用机制,并通过一个紫粉可爱风格的页面进行了实战演示。

核心要点回顾:

  1. @Component 封装完整组件,含状态和逻辑。
  2. @Builder 复用一段 UI 结构,支持按值和按引用传参。
  3. @BuilderParam 实现插槽机制,让组件内容可定制。
  4. @Styles 复用通用样式,@Extend 扩展特定组件样式。
  5. @Reusable 提升列表滚动性能。
  6. 组件复用的核心是"参数化设计",一份代码多种形态。

掌握组件复用是提升开发效率和代码质量的关键。下一篇我们将深入讲解 HarmonyOS 动画与转场技术。

Logo

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

更多推荐