本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新

   鸿蒙开发中,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+ 复杂交互场景

四、通用参数传递规则

所有参数传递方式都需遵守以下规则:

  1. 参数类型限制

    • 不允许为undefined、null

    • 不允许返回undefined、null的表达式

  2. 参数不可变

    • 在@Builder函数内部,不允许改变参数值(除非使用MutableBinding)

  3. 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装饰器配合实现深度观测

使用原则

  1. 简单场景:使用按值传递

  2. 需要刷新:使用按引用传递(单个参数)

  3. 需要修改参数:使用按回调传递(API 20+)

  4. 复杂UI:考虑抽取为自定义组件

避坑

  • 记住按引用传递只能一个参数

  • MutableBinding必须配setter

  • 不要在UI语句外调用@Builder

  • 不要在@Watch中调用@Builder

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐