鸿蒙开发中@Builder与@BuilderParam深度解析与实战

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

在鸿蒙(HarmonyOS)应用开发中,ArkUI框架提供了两种强大的UI复用机制:@Builder装饰器和@BuilderParam装饰器。这两种机制从不同角度解决了UI组件复用和灵活配置的问题。本文将全面介绍这两种装饰器的使用方式,从基础概念到高级应用,结合实例代码介绍鸿蒙UI开发中的高效复用技巧。

一、@Builder装饰器基础与应用

1. @Builder装饰器概述

@Builder是ArkUI提供的一种轻量级UI元素复用机制,它通过将重复使用的UI结构抽象为函数来实现代码复用。主要特点包括:

  • 组件内复用:减少重复代码,提高可维护性
  • 全局共享:支持跨组件复用UI模板
  • 动态更新:配合状态变量实现UI自动刷新

@Builder装饰的函数遵循与build()函数相同的语法规则,开发者可以在build方法中直接调用这些自定义构建函数。

2. @Builder的两种类型

2.1 私有自定义构建函数

私有构建函数定义在组件内部,通过this访问组件状态,只能在所属组件内调用。

@Entry
@Component
struct PrivateBuilderExample {
  @State count: number = 0;

  // 定义私有构建函数
  @Builder
  CounterButton() {
    Button(`点击次数: ${this.count}`)
      .onClick(() => this.count++)
      .margin(10)
  }

  build() {
    Column() {
      this.CounterButton() // 第一次调用
      this.CounterButton() // 重复使用
      Text("私有构建函数只能通过this访问")
        .fontSize(16)
    }
    .width('100%')
  }
}

 关键点

  • 使用@Builder装饰器修饰方法
  • 通过this.方法名()调用
  • 可以访问组件内的状态变量(如@State)
  • 只能在组件内部使用 
2.2 全局自定义构建函数

全局构建函数在组件外定义,可以被整个应用访问,不与具体组件状态绑定。

// 全局定义构建函数
@Builder
function GlobalButton(text: string, color: Color) {
  Button(text)
    .width(200)
    .height(50)
    .backgroundColor(color)
    .fontColor(Color.White)
}

@Entry
@Component
struct GlobalBuilderExample {
  build() {
    Column() {
      GlobalButton('确认', Color.Green) // 使用全局构建函数
      GlobalButton('取消', Color.Red)
      GlobalButton('帮助', Color.Blue)
    }
    .width('100%')
    .padding(20)
  }
}

关键点

  • 使用@Builder function语法定义
  • 直接通过函数名调用,无需this
  • 不能访问组件状态(无this)
  • 适合无状态UI复用 

3. @Builder参数传递机制

@Builder支持两种参数传递方式:按值传递和按引用传递,这对UI更新行为有重要影响。

3.1 按值传递(默认)

按值传递参数时,传递的是值的副本,状态变量更新不会触发@Builder内部UI刷新。

@Builder
function ValueTransferDemo(param: string) {
  Text(param) // 值拷贝,外部状态变化不会更新
    .fontSize(20)
    .margin(10)
}

@Entry
@Component
struct ValueExample {
  @State message: string = "初始值"

  build() {
    Column() {
      ValueTransferDemo(this.message)
      Button("更新值")
        .onClick(() => {
          this.message = "更新后的值 " + new Date().getTime()
        })
    }
    .width('100%')
  }
}

行为表现

  • 点击按钮后message状态变化
  • 但Text内容不会更新,因为传递的是初始值的副本
  • 适用于静态内容展示 
3.2 按引用传递

按引用传递时,传递的是状态变量的引用,状态变化会触发@Builder内部UI刷新。

class UpdateParam {
  content: string = ''
}

@Builder
function ReferenceTransferDemo($$: UpdateParam) {
   Text($$.content) // 引用传递,外部状态变化会更新
    .fontSize(20)
    .margin(10)
}

@Entry
@Component
struct ReferenceExample {
  @State param: UpdateParam = new UpdateParam()

  build() {
    Column() {
      ReferenceTransferDemo({ content: this.param.content })
      Button("更新值")
        .onClick(() => {
          this.param.content = "更新值 " + new Date().getTime()
        })
    }
    .width('100%')
  }
}

关键点

  • 使用对象字面量传递参数
  • 参数名前加$$是推荐命名约定(非强制) - 状态变化会自动更新UI - 适用于动态内容展示

二、@BuilderParam装饰器深入解析

1. @BuilderParam概述

@BuilderParam装饰器用于修饰自定义组件中的函数类型属性,允许父组件向子组件传递UI结构 。主要特点:

类似插槽机制:类似Vue中的slot,实现UI结构注入

灵活配置:子组件提供占位,父组件决定具体内容

类型安全:确保传递的构建函数符合预期 

 2. @BuilderParam基础用法

2.1 基本使用流程
  // 子组件定义
 @Component
 struct ChildComponent {
 
    @BuilderParam
     customUI: () => void
 
    build() {
         Column() {
             Text("子组件固定内容")
             .fontSize(20) 
             this.customUI() // 父组件传入的UI结构 
         }
         .borderWidth(1)
         .padding(10) 
    } 
} 


 // 父组件使用
 @Entry
 @Component
 struct ParentComponent {
 
    @Builder
     customBuilder() {
         Row() {
             Text("父组件传入的内容")
             .fontColor(Color.Red)

             Image($r('app.media.icon'))
             .width(30)
             .height(30)
         } 
    } 

    build() {
     Column() { 
        ChildComponent({ customUI: this.customBuilder // 传入构建函数 })
     }
     .width('100%') 
    } 
} 

关键点

         1. 子组件使用@BuilderParam定义占位属性

         2. 父组件使用@Builder定义构建函数

         3. 初始化子组件时传入构建函数

         4. 子组件在build中调用传入的函数

 2.2 初始化方式

         @BuilderParam可以通过三种方式初始化 :

         方式一:本地初始化(使用组件内构建函数) 

@Component
 struct Child {

     @Builder
     localBuilder() {

     }

     @BuilderParam
     builderParam: () => void = this.localBuilder

 } 
方式二:全局构建函数初始化 
@Builder
function GlobalBuilder() {

} 

@Component struct Child { 

    @BuilderParam
    builderParam: () => void = GlobalBuilder 
} 
方式三:父组件构建函数初始化(最常用)
@Component 
struct Parent { 

    @Builder 
    parentBuilder() {

    } 

    build() { 
        Column() { 
            Child({ builderParam: this.parentBuilder }) 
        } 
    } 
} 

3. @BuilderParam高级用法

3.1 带参数的构建函数传递

@BuilderParam可以接收带参数的构建函数,实现更灵活的UI配置 。

// 子组件 
@Component 
struct ParamChild { 
    @BuilderParam 
    paramBuilder: (color: Color) => void 

    build() { 
        Column() { 
            this.paramBuilder(Color.Red) // 传入参数 
            this.paramBuilder(Color.Blue) 
        } 
    } 
} 

// 父组件 
@Entry 
@Component 
struct ParamParent { 
    @Builder 
    colorBlock($$: { color: Color }) { 
        Row() { Text("彩色区块") 
            .fontColor(Color.White) 
        } 
        .width(100) 
        .height(100) 
        .backgroundColor($$.color) 
    } 

    build() { 
        Column() { 
            ParamChild({ 
                paramBuilder: (color: Color) => { 
                                this.colorBlock({ color: color }) 
                                } 
            }) 
        } 
    } 
} 
3.2 尾随闭包语法

当自定义组件只有一个@BuilderParam属性时,可以使用更简洁的尾随闭包语法 。

// 子组件 
@Component 
struct TrailingChild { 
    @BuilderParam content: () => void 
    build() { 
        Column() { 
            Text("标题") 
            .fontSize(20) 
            this.content() 
        } 
    } 
} 

// 父组件 
@Entry 
@Component 
struct TrailingParent { 

    build() { 
        Column() { 
            // 尾随闭包写法 
            TrailingChild() {
                 Column() { 
                    Text("内容1") 
                    Text("内容2") 
                 } 
            } 
        } 
    } 
} 

 限制条件

- 组件只能有一个@BuilderParam属性

- 不能有其他构造参数

- 语法更简洁但灵活性降低

三、@Builder与@BuilderParam对比

1. 核心区别

特性@Builder@BuilderParam
目的UI结构复用UI结构注入(类似插槽)
定义位置组件内或全局只能组件内
初始化直接定义实现需要外部传入
状态访问私有构建函数可访问组件状态取决于传入的构建函数
使用场景内部UI复用组件自定义内容区域
参数传递支持值和引用传递依赖传入的构建函数
灵活性相对固定更高灵活性

2. 选择依据

使用@Builder当:
  • 需要复用组件内部的UI片段
  • 逻辑和UI紧密耦合
  • 不需要外部定制
使用@BuilderParam当:
  • 组件需要开放部分UI给外部定制
  • 创建通用容器组件
  • 需要类似插槽的功能

四、实战应用场景

1. 动态列表项定制(使用@BuilderParam)

场景:创建通用列表组件,允许父组件自定义每个列表项的UI样式

// 子组件定义
@Component
struct CustomList {
  @BuilderParam itemBuilder: (item: string, index: number) => void
  @State items: string[] = ['苹果', '香蕉', '橙子']

  build() {
    List() {
      ForEach(this.items, (item: string, index: number) => {
        ListItem() {
          this.itemBuilder(item, index)
        }
      })
    }
  }
}

// 父组件使用
@Entry
@Component
struct ParentComponent {
  @Builder
  customItem(item: string, index: number) {
    Row() {
      Image($r(`app.media.icon_${index}`))
        .width(30)
        .height(30)
      Text(`${index + 1}. ${item}`)
        .fontSize(16)
    }
    .padding(10)
  }

  build() {
    Column() {
      CustomList({
        itemBuilder: this.customItem
      })
    }
  }
}

关键点

  • 子组件通过@BuilderParam开放UI定制能力
  • 父组件通过@Builder定义具体样式
  • 支持传递多个参数(item和index) 
2. 可配置卡片组件(组合使用@Builder和@BuilderParam)

场景:构建带标题、内容和底部操作区的卡片组件

// 卡片组件定义
@Component
struct ConfigurableCard {
  @Prop title: string
  @BuilderParam content: () => void
  @BuilderParam footer: () => void

  @Builder
  private cardStyle(content: () => void) {
    Column() {
      content()
    }
    .borderRadius(10)
    .backgroundColor(Color.White)
    .shadow(5)
  }

  build() {
    Column() {
      // 标题区(固定样式)
      Text(this.title)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 10 })

      // 内容区(可定制)
      this.cardStyle(this.content)

      // 底部区(可定制)
      this.cardStyle(this.footer)
    }
    .width('90%')
    .padding(15)
  }
}

// 使用示例
@Entry
@Component
struct CardExample {
  @State data: string = "卡片内容数据"

  @Builder
  contentBuilder() {
    Text(this.data)
      .fontSize(16)
      .margin(10)
  }

  @Builder
  footerBuilder() {
    Row() {
      Button('确认').width(100)
      Button('取消').width(100)
    }
    .justifyContent(FlexAlign.SpaceAround)
  }

  build() {
    Column() {
      ConfigurableCard({
        title: "示例卡片",
        content: this.contentBuilder,
        footer: this.footerBuilder
      })
    }
  }
}

优势

  • 固定样式通过@Builder复用
  • 可变区域通过@BuilderParam开放 
3. 带状态管理的表单控件

场景:构建可复用表单输入组件,支持自定义验证逻辑

// 表单组件
@Component
struct FormInput {
  @Prop label: string
  @State inputValue: string = ''
  @BuilderParam validator: (value: string) => void

  build() {
    Column() {
      Text(this.label)
        .fontSize(16)
        .margin({ bottom: 5 })

      TextInput({ text: this.inputValue })
        .onChange((value: string) => {
          this.inputValue = value
          this.validator(value)
        })

      // 验证结果显示区
      this.validator(this.inputValue)
    }
    .margin({ bottom: 15 })
  }
}

// 使用示例
@Entry
@Component
struct FormExample {
  @Builder
  emailValidator(value: string) {
    if (value && !value.includes('@')) {
      Text("邮箱格式不正确")
        .fontSize(12)
        .fontColor(Color.Red)
    }
  }

  build() {
    Column() {
      FormInput({
        label: "电子邮箱",
        validator: (value) => {
          this.emailValidator(value)
        }
      })
    }
    .padding(20)
  }
}

特点

  • 子组件管理输入状态
  • 验证逻辑由父组件定义 
4. 复合式弹窗组件

场景:构建可定制内容、按钮的弹窗

@Component
struct CustomDialog {
  @BuilderParam content: () => void
  @BuilderParam buttons: () => void

  @Builder
  private dialogStyle(content: () => void) {
    Column() {
      content()
    }
    .width('80%')
    .padding(20)
    .backgroundColor(Color.White)
  }

  build() {
    Column() {
      // 内容区
      this.dialogStyle(this.content)

      // 按钮区
      Row() {
        this.buttons()
      }
      .margin({ top: 20 })
    }
  }
}

// 使用示例
@Entry
@Component
struct DialogExample {
  @State showDialog: boolean = false

  @Builder
  dialogContent() {
    Text("确定要删除此项吗?")
      .fontSize(18)
  }

  @Builder
  dialogButtons() {
    Button("取消")
      .onClick(() => this.showDialog = false)
    Button("确定")
      .onClick(() => {
        // 处理确认逻辑
        this.showDialog = false
      })
  }

  build() {
    Column() {
      Button("显示弹窗")
        .onClick(() => this.showDialog = true)

      if (this.showDialog) {
        CustomDialog({
          content: this.dialogContent,
          buttons: this.dialogButtons
        })
      }
    }
  }
}

 五、常见问题

1. 问题总结:

     0. 命名规范

                1. 按引用传递参数使用$$前缀

                 2. 构建函数使用动词+名词形式,如buildButtonrenderHeader

  1. 性能优化

    • 复杂UI考虑使用@BuilderParam分拆组件
    • 频繁更新的UI使用按引用传递
  2. 代码组织

    • 相关构建函数集中管理
    • 全局构建函数单独文件存放
  3. 状态管理

    • 避免在构建函数内直接修改参数
    • 需要状态联动时使用@Link或按引用传递
  4. 组合使用

    • 在自定义组件内同时使用@Builder和@BuilderParam
    • @Builder处理固定部分,@BuilderParam处理可变部分

2. 常见问题与解决方案

问题1:构建函数内状态更新无效

现象:在@Builder内修改状态变量,UI不更新

原因:直接修改值传递的参数或未使用正确状态管理

解决

// 错误做法
@Builder
function ProblemDemo(param: string) {
  Button(param)
    .onClick(() => param = "新值") // 无效
}

// 正确做法1:使用按引用传递
@Builder
function Solution1Demo($$: {param: string}) {   Button($$.param)
    .onClick(() => $$.param = "新值") // 有效
}

// 正确做法2:使用@Link
@Builder
function Solution2Demo(param: string) {
  Button(param)
    .onClick(() => this.param = "新值") // 需要param是@Link
}

问题2:@BuilderParam类型不匹配

现象:父组件传入的构建函数与@BuilderParam声明类型不一致

解决

// 子组件
@Component
struct Child {
  // 声明接收带string参数的构建函数
  @BuilderParam builderParam: (text: string) => void
}

// 父组件
@Entry
@Component
struct Parent {
  @Builder
  stringBuilder(text: string) { // 参数类型匹配
    Text(text)
  }
  
  build() {
    Column() {
      Child({ builderParam: this.stringBuilder })
    }
  }
}

问题3:尾随闭包语法冲突

现象:组件有多个@BuilderParam属性时无法使用尾随闭包

解决

// 子组件
@Component
struct ConflictChild {
  @BuilderParam header: () => void
  @BuilderParam content: () => void // 多个BuilderParam
  
  build() {
    Column() {
      this.header()
      this.content()
    }
  }
}

// 父组件必须使用显式初始化
@Entry
@Component
struct ConflictParent {
  @Builder
  buildHeader() { /*...*/ }
  
  @Builder
  buildContent() { /*...*/ }

  build() {
    Column() {
      ConflictChild({
        header: this.buildHeader,
        content: this.buildContent
      })
    }
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值