鸿蒙中 @Builder装饰器:自定义构建函数
定义在自定义组件内部的@Builder函数,是该组件的私有成员函数。@Entry@Component// 无参数的私有@Builder函数@Builder// 带参数的私有@Builder函数@Builderbuild() {Column() {// 调用无参数@Builder// 调用带参数@Builder调用方式在自定义组件内、build函数中调用在其他自定义构建函数中调用使用this关键字访
本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新
鸿蒙开发中,UI代码的复用是一个核心需求。ArkUI提供了轻量的UI元素复用机制@Builder装饰器,它允许将重复使用的UI结构抽象成函数,在build函数中调用,从而大幅提升代码复用性和可维护性。
一、@Builder装饰器
@Builder装饰器用于封装可复用的UI结构,通过提取重复的布局代码提高开发效率。被@Builder装饰的函数也称为"自定义构建函数"。
1.2 与@Component的主要差异
| 特性 | @Builder | @Component |
|---|---|---|
| 功能定位 | 轻量UI复用 | 复杂UI组件封装 |
| 状态变量 | 不支持定义 | 支持定义 |
| 生命周期 | 不支持 | 完整支持 |
| 数据交互 | 通过参数传递 | 通过状态变量 |
1.3 支持版本
| 版本 | 支持范围 |
|---|---|
| API version 7 | 开始支持 |
| API version 9+ | 支持在ArkTS卡片中使用 |
| API version 11+ | 支持在元服务中使用 |
二、@Builder的两种定义方式
2.1 私有自定义构建函数
定义在自定义组件内部的@Builder函数,是该组件的私有成员函数。
@Entry
@Component
struct BuilderDemo {
// 无参数的私有@Builder函数
@Builder
showTextBuilder() {
Text('Hello World')
.fontSize(30)
.fontWeight(FontWeight.Bold)
}
// 带参数的私有@Builder函数
@Builder
showTextValueBuilder(param: string) {
Text(param)
.fontSize(30)
.fontWeight(FontWeight.Bold)
}
build() {
Column() {
// 调用无参数@Builder
this.showTextBuilder()
// 调用带参数@Builder
this.showTextValueBuilder('Hello @Builder')
}
}
}
调用方式:
-
在自定义组件内、build函数中调用
-
在其他自定义构建函数中调用
-
使用
this关键字访问
2.2 全局自定义构建函数
定义在组件外部的@Builder函数,可以在任何组件中使用。
// 全局@Builder函数
@Builder
function showTextBuilder() {
Text('Hello World')
.fontSize(30)
.fontWeight(FontWeight.Bold)
}
@Entry
@Component
struct BuilderSample {
build() {
Column() {
// 直接调用全局@Builder
showTextBuilder()
}
}
}
适用场景:不涉及组件状态变量变化的情况,建议使用全局自定义构建函数。
三、参数传递规则
@Builder支持三种参数传递方式,每种方式都有特定的使用场景和刷新机制。
3.1 按值传递(默认方式)
@Builder
function overBuilderByValue(paramA1: string) {
Row() {
Text(`UseStateVarByValue: ${paramA1} `)
}
}
@Entry
@Component
struct ParameterValue {
@State label: string = 'Hello';
build() {
Column() {
// 按值传递
overBuilderByValue(this.label)
}
}
}
特点:
-
默认的传递方式
-
状态变量的改变不会引起@Builder函数内的UI刷新
-
不推荐在使用状态变量时使用
3.2 按引用传递
class Tmp {
public paramA1: string = '';
}
@Builder
function overBuilderByReference(params: Tmp) {
Row() {
Text(`UseStateVarByReference: ${params.paramA1} `)
}
}
@Entry
@Component
struct ParameterReference {
@State label: string = 'Hello';
build() {
Column() {
// 按引用传递:对象字面量形式
overBuilderByReference({ paramA1: this.label })
Button('Click me').onClick(() => {
this.label = 'ArkUI'; // UI会自动刷新
})
}
}
}
特点:
-
传递的参数可为状态变量
-
状态变量的改变会引起@Builder函数内的UI刷新
-
必须使用对象字面量形式传递参数
-
重要限制:只支持传入一个参数时才生效
3.3 按回调传递(API 20+)
从API version 20开始,支持通过UIUtils.makeBinding()函数实现更灵活的刷新机制。
import { Binding, MutableBinding, UIUtils } from '@kit.ArkUI';
@Builder
function customButton(num1: Binding<number>, num2: MutableBinding<number>) {
Row() {
Column() {
Text(`number1: ${num1.value}, number2: ${num2.value}`)
Button(`only change number2`)
.onClick(() => {
// 修改MutableBinding类型,会同步到父组件
num2.value += 1;
})
}
}
}
@Entry
@ComponentV2
struct ParameterMakeBinding {
@Local number1: number = 5;
@Local number2: number = 12;
build() {
Column() {
customButton(
// Binding类型:只读,支持UI刷新
UIUtils.makeBinding<number>(() => this.number1),
// MutableBinding类型:可读写,支持UI刷新并同步修改
UIUtils.makeBinding<number>(
() => this.number2,
(val: number) => {
this.number2 = val;
}
)
)
}
}
}
参数类型说明:
| 类型 | 创建方式 | 特性 |
|---|---|---|
| Binding<T> | UIUtils.makeBinding(getter) | 只读,支持UI刷新 |
| MutableBinding<T> | UIUtils.makeBinding(getter, setter) | 可读写,支持UI刷新并同步修改 |
3.4 三种传递方式对比
| 传递方式 | 支持版本 | UI刷新 | 修改参数 | 适用场景 |
|---|---|---|---|---|
| 按值传递 | API 7+ | ❌ | ❌ | 静态数据 |
| 按引用传递 | API 7+ | ✅ | ❌ | 单个状态变量 |
| 按回调传递 | API 20+ | ✅ | ✅ | 复杂交互场景 |
四、通用参数传递规则
所有参数传递方式都需遵守以下规则:
-
参数类型限制
-
不允许为undefined、null
-
不允许返回undefined、null的表达式
-
-
参数不可变
-
在@Builder函数内部,不允许改变参数值(除非使用MutableBinding)
-
-
UI语法遵循
-
@Builder内UI语法遵循标准的UI语法规则
-
五、高级使用场景
5.1 将@Builder作为CustomBuilder类型使用
当参数类型为CustomBuilder时,可以传入定义的@Builder函数。
@Builder
function overBuilderDemo() {
Row() {
Text('Global Builder')
.fontSize(30)
.fontWeight(FontWeight.Bold)
}
}
@Entry
@Component
struct customBuilderDemo {
@State arr: number[] = [0, 1, 2, 3, 4];
@Builder
privateBuilder() {
Row() {
Text('Private Builder')
.fontSize(30)
.fontWeight(FontWeight.Bold)
}
}
build() {
Column() {
List({ space: 10 }) {
ForEach(this.arr, (item: number) => {
ListItem() {
Text(`${item}`)
.width('100%')
.height(100)
}
.swipeAction({
start: {
builder: overBuilderDemo() // 全局@Builder
},
end: {
builder: () => {
this.privateBuilder() // 局部@Builder
}
}
})
})
}
}
}
}
5.2 多层@Builder函数嵌套
通过按引用传递的方式,实现多层@Builder的动态UI刷新。
class ThisTmp {
public paramA1: string = '';
}
// 第一层
@Builder
function parentBuilder($$: ThisTmp) {
Row() {
Column() {
Text(`parentBuilder===${$$.paramA1}`)
// 调用自定义组件
HelloComponent({ message: $$.paramA1 })
// 调用第二层@Builder
childBuilder({ paramA1: $$.paramA1 })
}
}
}
@Component
struct HelloComponent {
@Prop message: string = '';
build() {
Row() {
Text(`HelloComponent===${this.message}`)
.width(300)
.height(40)
.margin(10)
.backgroundColor('#0d000000')
.fontColor('#e6000000')
.borderRadius(20)
.textAlign(TextAlign.Center)
}
}
}
// 第二层
@Builder
function childBuilder($$: ThisTmp) {
Row() {
Column() {
Text(`childBuilder===${$$.paramA1}`)
// 调用第三层@Builder
grandsonBuilder({ paramA1: $$.paramA1 })
}
}
}
// 第三层
@Builder
function grandsonBuilder($$: ThisTmp) {
Row() {
Column() {
Text(`grandsonBuilder===${$$.paramA1}`)
}
}
}
@Entry
@Component
struct ParentExample {
@State label: string = 'Hello';
build() {
Column() {
parentBuilder({ paramA1: this.label })
Button('Click me').onClick(() => {
this.label = 'ArkUI'; // 所有层级的UI都会刷新
})
}
}
}
关键点:每层调用@Builder的地方必须使用按引用传递方式(使用$$参数名,但非必须)。
5.3 @Builder联合V2装饰器
场景一:与@ObservedV2/@Trace配合
@ObservedV2
class Info {
@Trace public name: string;
@Trace public age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
@Builder
function overBuilderTest(param: Info) {
Column() {
Text(`Global@Builder name: ${param.name}`)
Text(`Global@Builder age: ${param.age}`)
}
}
@Entry
@ComponentV2
struct ParentPage {
info: Info = new Info('Tom', 25);
build() {
Column() {
// 值传递方式(注意:不能使用引用传递)
overBuilderTest(this.info)
Button('change')
.onClick(() => {
this.info.name = 'Cat'; // UI会刷新
this.info.age = 18;
})
}
}
}
特点:
-
使用值传递方式(引用传递会被语法拦截)
-
@Trace装饰的属性变化会触发UI刷新
场景二:与@Local配合
class LocalInfo {
public name: string = 'Tom';
public age: number = 25;
}
@Builder
function overBuilderLocal(param: LocalInfo) {
Column() {
Text(`Global@Builder name: ${param.name}`)
Text(`Global@Builder age: ${param.age}`)
}
}
@Entry
@ComponentV2
struct ParentLocalPage {
@Local LocalInfo2: LocalInfo = { name: 'Tom', age: 25 };
build() {
Column() {
// 引用传递方式
overBuilderLocal({ name: this.LocalInfo2.name, age: this.LocalInfo2.age })
Button('change')
.onClick(() => {
this.LocalInfo2 = { name: 'Cat', age: 18 }; // UI会刷新
})
}
}
}
特点:
-
@Local装饰的对象整体赋值会触发UI刷新
-
未被装饰的对象赋值不会触发刷新
5.4 跨组件复用的全局@Builder
class ReusableTmp {
public componentName: string = 'Child';
}
@Builder
function itemBuilder(params: ReusableTmp) {
Column() {
Text(`Builder ===${params.componentName}`)
}
}
@Reusable
@Component
struct ReusableChildPage {
@State message: string = 'Child';
aboutToReuse(params: Record<string, ESObject>): void {
this.message = params.message;
}
build() {
Column() {
Text(`ReusableChildPage ===${this.message}`)
// 调用全局@Builder
itemBuilder({ componentName: this.message })
}
}
}
@Entry
@Component
struct ReusablePage {
@State switchFlag: boolean = true;
build() {
Column() {
if (this.switchFlag) {
ReusableChildPage({ message: 'Child' })
} else {
ReusableChildTwoPage({ message: 'ChildTwo' })
}
Button('Click me')
.onClick(() => {
this.switchFlag = !this.switchFlag;
})
}
}
}
六、限制
核心限制条件
| 限制项 | 说明 | 解决方案 |
|---|---|---|
| 参数修改 | 不允许修改参数值 | 使用MutableBinding |
| 参数个数 | 按引用传递只支持单个参数 | 合并多个参数到一个对象 |
| 参数类型 | 不允许undefined/null | 确保参数有有效值 |
| 调用位置 | 不能在UI语句外调用 | 直接在UI方法中调用 |
常见问题
问题一:两个以上参数不刷新
错误示例:
@Builder
function overBuilder1(param: GlobalTmp1, num: number) { // 两个参数
Column() {
Text(`strValue: ${param.strValue}`)
Text(`num: ${num}`)
}
}
// 值的改变不会触发UI刷新
备注:当存在两个或两个以上的参数时,即使通过对象字面量形式传递,值的改变也不会触发UI刷新。
正确做法:
class GlobalTmp3 {
public strValue: string = 'Hello';
public numValue: number = 0;
}
@Builder
function overBuilder3(param: GlobalTmp3) { // 单个参数
Column() {
Text(`strValue: ${param.strValue}`)
Text(`num: ${param.numValue}`)
}
}
备注:@Builder只接受一个参数。当传入一个参数的时候,通过对象字面量的形式传递,值的改变会引起UI的刷新。
问题二:在@Builder内修改参数值
错误示例:
@Builder
function overBuilderMod1(param: TempMod1) {
Button(`overBuilderMod1 === ${param.paramA}`)
.onClick(() => {
param.paramA = 'Yes'; // 错误!会导致运行时错误
})
}
备注:不使用MutableBinding的情况下,在@Builder装饰的函数内部修改参数值,修改不会生效且可能造成运行时错误。
正确做法:
import { UIUtils, MutableBinding } from '@kit.ArkUI';
@Builder
function overBuilderMod2(param: MutableBinding<TempMod2>) {
Button(`overBuilder === ${param.value.paramA}`)
.onClick(() => {
param.value.paramA = 'Yes'; // 正确
})
}
// 调用时
overBuilderMod2(
UIUtils.makeBinding<TempMod2>(
() => this.objectOne,
value => {
this.objectOne = value; // 必须传递setter
}
)
)
问题三:在UI语句外调用@Builder
错误示例:
private bgList: Array<CustomBuilder> = [this.myImages(), this.myImages2()]; // 错误
@State bgBuilder: CustomBuilder = this.myImages(); // 错误
正确做法:
build() {
Column() {
// 直接在UI方法中调用
Text('Text').background(this.myImages) // 传递@Builder方法
Text('Text').background(this.myImages()) // 调用@Builder方法
}
}
问题四:在@Builder内创建自定义组件传参不刷新
错误示例:
class Tmp4 {
public name: string = 'Hello';
public age: number = 66;
}
@Builder
function parentBuilder1(params: Tmp4) {
Row() {
Column() {
Text(`parentBuilder1===${params.name}===${params.age}`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
// 此写法不属于按引用传递方式,用法错误导致UI不刷新。
HelloComponent1({ info: params })
}
}
}
@Component
struct HelloComponent1 {
@Prop info: Tmp4 = new Tmp4();
build() {
Row() {
Text(`HelloComponent1===${this.info.name}===${this.info.age}`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
}
}
@Entry
@Component
struct ParentPage1 {
@State nameValue: string = 'Zhang San';
@State ageValue: number = 18;
build() {
Column() {
parentBuilder1({ name: this.nameValue, age: this.ageValue })
Button('Click me')
.onClick(() => {
// 此处修改内容时,不会引起HelloComponent1处的变化
this.nameValue = 'Li';
this.ageValue = 20;
})
}
.height('100%')
.width('100%')
}
}
正确做法:
class Tmp5 {
public name: string = 'Hello';
public age: number = 16;
}
@Builder
function parentBuilder2(params: Tmp5) {
Row() {
Column() {
Text(`parentBuilder2===${params.name}===${params.age}`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
// 将整个对象拆分开变成简单类型,属于按引用传递方式,更改属性能够触发UI刷新。
HelloComponent2({ childName: params.name, childAge: params.age })
}
}
}
@Component
struct HelloComponent2 {
@Prop childName: string = '';
@Prop childAge: number = 0;
build() {
Row() {
Text(`HelloComponent2===${this.childName}===${this.childAge}`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
}
}
@Entry
@Component
struct ParentPage2 {
@State nameValue: string = 'Zhang San';
@State ageValue: number = 18;
build() {
Column() {
parentBuilder2({ name: this.nameValue, age: this.ageValue })
Button('Click me')
.onClick(() => {
// 此处修改内容时,会引起HelloComponent2处的变化
this.nameValue = 'Li Si';
this.ageValue = 20;
})
}
.height('100%')
.width('100%')
}
}
总结
核心能力:
-
轻量级UI复用机制
-
支持私有和全局两种定义方式
-
三种参数传递方式适应不同场景
-
与V2装饰器配合实现深度观测
使用原则:
-
简单场景:使用按值传递
-
需要刷新:使用按引用传递(单个参数)
-
需要修改参数:使用按回调传递(API 20+)
-
复杂UI:考虑抽取为自定义组件
避坑:
-
记住按引用传递只能一个参数
-
MutableBinding必须配setter
-
不要在UI语句外调用@Builder
-
不要在@Watch中调用@Builder
更多推荐




所有评论(0)