HarmonyOS 自定义构建函数:@Builder 与 @BuilderParam 的组件复用范式
·
HarmonyOS 自定义构建函数:@Builder 与 @BuilderParam 的组件复用范式
前言
在 ArkUI 里,当多个页面的某块 UI 结构高度相似、只有内部细节(标题、图标、点击行为)不同时,直接复制粘贴组件会迅速失控。@Builder 与 @BuilderParam 是 ArkTS 提供的「自定义构建函数」机制:前者把一段 UI 抽成可复用函数,后者允许父组件向子组件注入一段 UI 插槽。两者组合,是构建「高内聚、低耦合」通用容器组件(卡片、列表项、对话框、导航栏)的基石。本文用可运行示例讲透它们的用法、参数传递、this 绑定陷阱与插槽注入模式。
问题描述
常见痛点:
- 写了三个「信息卡片」,只有标题和图标不同,却复制了三份
Column结构,改样式要改三处。 - 想做一个通用
Dialog,但「确认按钮区域」希望由调用方自定义,不知道怎么传一段 UI 进去。 - 在
@Builder里调用this.someState拿到的是undefined——因为自定义构建函数默认不绑定组件实例的this。 - 把
@Builder当回调传给子组件时,函数内的状态读取错乱。
本质问题:ArkUI 的 UI 复用有两种层次——「整段 UI 复用」用 @Builder,「组件内部留一个可替换的 UI 插槽」用 @BuilderParam。
细节解析
1. @Builder:局部 UI 函数
- 全局
@Builder function与组件内成员 @Builder两种写法。 - 成员
@Builder需要通过this.myBuilder()调用才能访问组件状态;全局@Builder无this。 - 可带参数,参数支持普通值与「按引用传递的状态」(传状态可实现联动刷新)。
2. @BuilderParam:组件插槽
- 在子组件里声明
@BuilderParam content: () => void,父组件通过content={this.xxxBuilder}注入一段 UI。 - 注入的
@Builder若在父组件定义,其this指向父组件,天然能读到父组件状态。 - 这是 ArkUI 实现「容器组件 + 内容插槽」的标准方式,等价于其他框架的 children / slot。
3. this 绑定规则(关键坑)
| 写法 | this 指向 | 能否读状态 |
|---|---|---|
全局 @Builder function | 无(全局) | 否 |
成员 @Builder 用 this.x() 调用 | 当前组件 | 能 |
@BuilderParam 由父组件传入 | 父组件 | 能(父的状态) |
4. 参数按值 vs 按引用
给 @Builder 传普通参数按值捕获;若传 @State/@Local 变量并希望联动刷新,需直接传该状态变量(引用语义),框架会建立依赖。
示例代码(可运行 ArkTS/ArkUI)
示例 1:成员 @Builder 复用卡片
@ComponentV2
struct InfoCard {
@Local title: string = '';
@Local icon: ResourceStr = $r('app.media.startIcon');
@Builder
cardHeader() {
// 成员 Builder,this 指向 InfoCard,能读 this.title
Row({ space: 8 }) {
Image(this.icon).width(24).height(24)
Text(this.title).fontSize(16).fontWeight(FontWeight.Bold)
}
}
build() {
Column({ space: 10 }) {
this.cardHeader()
Text('这里是卡片正文内容。')
}
.padding(16)
.backgroundColor('#F5F5F5')
.borderRadius(12)
}
}
示例 2:@BuilderParam 做可定制插槽
// 通用容器:标题栏 + 可注入的内容插槽
@ComponentV2
struct Panel {
@Param title: string = '面板';
@BuilderParam body: () => void = () => {}; // 默认空实现
build() {
Column({ space: 12 }) {
Text(this.title).fontSize(18).fontWeight(FontWeight.Bold)
this.body() // 父组件注入的 UI 在这里渲染
}
.padding(16)
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({ radius: 6, color: '#10000000' })
}
}
@Entry
@ComponentV2
struct Page {
@Local count: number = 0;
@Builder
customBody() {
// 这里 this 指向 Page,能读 this.count
Column({ space: 8 }) {
Text(`当前计数:${this.count}`)
Button('+1').onClick(() => { this.count++; })
}
}
build() {
Column({ space: 16 }) {
Panel({ title: '统计面板' }) {
this.customBody()
}
}
.padding(20)
}
}
示例 3:带参数的 @Builder 与状态联动
@ComponentV2
struct TagList {
@Local tags: string[] = ['HarmonyOS', 'ArkTS', 'ArkUI'];
@Builder
tagItem(text: string, active: boolean) {
Text(text)
.padding({ left: 12, right: 12, top: 4, bottom: 4 })
.backgroundColor(active ? '#0A59F7' : '#E5E5E5')
.fontColor(active ? Color.White : Color.Black)
.borderRadius(14)
}
build() {
Row({ space: 8 }) {
ForEach(this.tags, (t: string) => {
this.tagItem(t, t === 'ArkTS') // 第二个参数按值,可改为状态联动
})
}
}
}
总结
@Builder抽「重复 UI」:成员写法用this.x()调用以绑定组件状态;全局写法适合纯展示、无状态的片段。@BuilderParam做「组件插槽」:容器组件对外暴露body/footer等插槽,父组件注入自定义 UI,实现高复用。this绑定是最大陷阱:成员@Builder必须this.x()调用;@BuilderParam注入的函数this属于父组件。- 状态联动要传引用:想让
@Builder内部随状态刷新,就直接传状态变量而非快照值。 - 组合使用:
@Builder负责「内部复用」,@BuilderParam负责「外部定制」,两者配合即可用少量代码搭出一套整齐的组件库。
掌握这套范式,你写的「列表项 / 卡片 / 弹窗 / 导航条」就能从「复制粘贴」升级为「一套组件 + N 种插槽」,后期维护成本直线下降。
更多推荐

所有评论(0)