在鸿蒙ArkUI声明式开发体系中,组件复用父子组件通信是从“基础页面编写”进阶到“工程化项目开发”的核心分水岭。规范化开发通过复用机制精简代码、统一UI风格,并通过组件通信实现页面数据驱动更新。

一、前言:为什么需要组件复用?

在开发电商首页、购物车、列表卡片等复杂页面时,页面中存在大量重复的样式、重复的UI结构、统一的交互逻辑。如果全部手写,会出现代码冗余、样式不统一、后期维护成本极高的问题。

ArkUI 提供了一套完整的复用方案:样式复用、UI结构复用、自定义组件复用,配合父子组件数据通信机制,能够实现高内聚、低耦合的前端开发模式,也是企业级鸿蒙项目模块化开发的基础。

二、三大复用装饰器对比与实战用法

(1)@Styles:全局通用样式复用

@Styles 用于抽取多个组件通用的公共样式,仅复用样式属性,不复用UI结构。适用于全局统一的字体、颜色、边距、对齐等基础样式。

@Styles 只能封装布局容器(Row/Column/Flex) 的链式样式,不能封装 Text、Button 等内部组件样式。 适合统一容器的宽、内边距、背景、对齐等。

核心特点:无组件绑定、全局可调用、轻量化、仅样式复用。

@Entry
@Component
struct Index {
  build() {
    Column({ space: 20 }) {
      Row() {
        Text("商品A")
        Text("数量:1")
      }
      .boxStyle()

      Row() {
        Text("商品B")
        Text("数量:5")
      }
      .boxStyle()
    }
    .pageStyle()
  }
}

@Styles function boxStyle() {
  .width('100%')
  .padding(15)
  .backgroundColor('#f5f5f5')
  .borderRadius(10)
}

@Styles function pageStyle() {
  .width('100%')
  .padding(20)
}

(2)@Extend:专属组件样式扩展

与 @Styles 不同,@Extend 绑定指定原生组件,只能作用于目标组件,可以扩展原生组件属性,甚至绑定专属交互逻辑。

核心特点:组件专属、可扩展原生能力、比 Styles 功能性更强。

适用场景:自定义专属Text文本、专属Button按钮,实现全局统一的定制化组件样式。

@Extend(Text) function textBase() {
  .fontSize(18)
  .fontColor('#333')
  .margin({ bottom: 8 })
}
@Extend(Button) function btnBase() {
  .width(140)
  .height(42)
  .borderRadius(6)
  .fontSize(17)
}

@Entry
@Component
struct Index {
  @State num: number = 0

  build() {
    Column({ space: 25 }) {
      Text('计数器演示')
        .textBase()

      Text(`当前数值:${this.num}`)
        .textBase()

      Row({ space: 15 }) {
        Button('-')
          .btnBase()
          .onClick(() => {
            if (this.num > 0) this.num--
          })
        Button('+')
          .btnBase()
          .onClick(() => {
            this.num++
          })
      }
    }
    .padding(25)
  }
}

(3)@Builder:UI结构片段复用

@Builder 是 ArkUI 中复用UI结构的核心方案,可以封装一段重复的组件结构,支持传参、支持动态数据渲染,是页面局部复用的最优解。

同时搭配 @BuilderParam 可以实现父组件向子组件注入自定义UI片段,实现高灵活度的组件插槽效果,极大提升组件的复用性和扩展性。

适用场景:列表卡片、通用标题栏、通用空数据页面、重复弹窗结构。

@Entry
@Component
struct Index {
  @State msg: string = '学习ArkUI'

  @Builder TitleText() {
    Text('我是标题')
      .fontSize(22)
      .fontWeight(FontWeight.Bold)
      .margin({ bottom: 10 })
  }

  @Builder GoodsCard(name: string, count: number) {
    Row() {
      Text(`商品:${name}`)
      Text(`数量:${count}`)
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
    .padding(15)
    .backgroundColor('#f5f5f5')
    .borderRadius(8)
    .margin({ bottom: 12 })
  }

  build() {
    Column() {
      this.TitleText()

      // 调用带参Builder,传入不同数据
      this.GoodsCard('笔记本', 2)
      this.GoodsCard('鼠标', 5)
      this.GoodsCard(this.msg, 1)

      Button('修改文字')
        .onClick(() => {
          this.msg = '鸿蒙组件学习'
        })
    }
    .padding(20)
  }
}

三、自定义组件与父子组件通信原理

单纯的结构复用只能解决代码冗余问题,想要实现数据驱动UI更新,必须依靠组件状态通信机制。在模块化开发中,页面会拆分为父组件与多个子组件,数据由父组件统一管理,子组件负责展示,通过单向数据流实现状态统一管理。

(1)核心通信装饰器:@Prop 单向传值

@Prop 用于实现父组件向子组件单向数据传递,是项目中最常用的组件通信方式。父组件使用 @State 定义状态变量,通过传参传递给子组件的 @Prop 变量。

核心机制:父组件数据更新 → 自动同步子组件 → 子组件UI自动刷新,子组件无法修改父组件数据,保证数据流单向可控,避免状态混乱。

@Component
struct NumberCount{
  @Prop num:number = 1
  subFn = () => {}
  addFn = () => {}
  build(){
    Row({space : 10}){
      Button('-')
        .onClick(() => {
          this.subFn()
        })
      Text(this.num.toString())
      Button('+')
        .onClick(() => {
          this.addFn()
        })
    }
    .padding(10)
  }
}
@Entry
@Component
@Preview
struct Test{
  @State num1:number = 5
  @State num2:number = 3
  build(){
    Column(){
      Row({space : 5}){
        Text('茄子')
        NumberCount({
          num:this.num1,
          subFn: () => this.num1--,
          addFn: () => this.num1++
        })
      }
      Row({space : 5}){
        Text('香蕉')
        NumberCount({
          num:this.num2,
          subFn: () => this.num2--,
          addFn: () => this.num2++
        })
      }
    }
    .padding(20)
  }
}

(2)@State 与 @Prop 职责区分

很多新手误区:子组件定义大量 @State 状态。实际工程规范中:

@State:仅用于组件内部私有状态,不对外暴露

@Prop:用于接收父组件传递的业务数据,只读、可响应更新

该机制保证了数据来源唯一,是复杂页面状态不乱、易于维护的关键。

(3)@Link 双向绑定

当子组件需要修改父组件的数据时(如表单输入、开关切换),使用 @Link 实现双向绑定。

需要注意的是:@Link修饰的变量是不能赋初始值的,@Prop是可以的。

@Entry
@Component
struct Index {
  @State num: number = 5

  build() {
    Column({ space: 30 }) {
      Text(`父组件当前数值:${this.num}`)
        .fontSize(22)
        .fontWeight(FontWeight.Medium)

      Counter1({
        count: this.num,
        min: 0,
        max: 10
      })
    }
    .width('100%')
    .padding(30)
  }
}

// 子组件:计数器
@Component
struct Counter1 {
  @Link count: number
  @Prop min: number = 0
  @Prop max: number = 10

  build() {
    Row({ space: 12 }) {
      Button('-')
        .width(36)
        .height(36)
        .borderRadius(18)
        .fontSize(20)
        .onClick(() => {
          if (this.count > this.min) {
            this.count--
          }
        })
      Text(`${this.count}`)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .width(36)
        .textAlign(TextAlign.Center)
      Button('+')
        .width(36)
        .height(36)
        .borderRadius(18)
        .fontSize(20)
        .onClick(() => {
          if (this.count < this.max) {
            this.count++
          }
        })
    }
    .height(48)
    .alignItems(VerticalAlign.Center)
  }
}

(4)@Provide 与 @Consume:跨层级传递

当组件嵌套层级较深时,使用 @Provide 和 @Consume 实现跨组件传递,避免逐层传递的繁琐。

这个也是双向连接的,就是互相之间可以修改数值。

class CountData {
  num: number = 0
}

@Component
struct ParentRoot {
  @Provide data: CountData = new CountData()

  build() {
    Column({ space: 20 }) {
      Text(`顶层数字:${this.data.num}`)
        .fontSize(22)
      MiddleBox()
    }
    .padding(30)
  }
}

@Component
struct MiddleBox {
  build() {
    Column({ space: 10 }) {
      Text("中间组件")
      ChildBox()
    }
  }
}

@Component
struct ChildBox {
  @Consume data: CountData

  build() {
    Row({ space: 10 }) {
      Button("-")
        .onClick(() => {
          if (this.data.num > 0) {
            this.data.num--
          }
        })
      Text(`${this.data.num}`)
        .fontSize(20)
      Button("+")
        .onClick(() => {
          this.data.num++
        })
    }
  }
}

@Entry
@Component
struct Index {
  build() {
    Column() {
      ParentRoot()
    }
  }
}

四、总结

三者复用方案各司其职:样式复用统一界面、Builder 结构复用精简代码、自定义组件+Prop和Link通信实现业务解耦。掌握这套体系后,已经具备开发中大型电商级复杂页面的能力,摆脱重复编码,写出规范、简洁、可维护的鸿蒙代码。

Logo

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

更多推荐