鸿蒙应用开发 @Builder 使用教程:从入门到精通

这是一个使用鸿蒙技术开发的本地原生记账应用,非常适合大家用来练手。相关源码已上传至 Github,点击此处查看项目。欢迎大家交流、指正,也欢迎提交 PR。

一、引言

在 HarmonyOS 应用开发中,UI 代码的复用性是提升开发效率和代码可维护性的关键。ArkTS 作为鸿蒙的声明式开发语言,提供了 @Builder 装饰器,专门用于构建可复用的 UI 片段。本文将深入浅出地介绍 @Builder 的核心概念、使用场景、参数传递机制以及最佳实践,帮助你彻底掌握这一重要工具。

二、@Builder 装饰器简介

@Builder 是 ArkTS(HarmonyOS 5.0.0+)中用于构建可复用 UI 片段的核心装饰器。它允许开发者将重复的 UI 结构抽象为独立的函数,提升代码的模块化和可维护性。@Builder 装饰的函数称为"自定义构建函数",遵循 ArkUI 的 build() 函数语法规则,用于返回一组 UI 描述。

主要特点

  • 复用性:封装重复的 UI 结构,避免代码冗余。
  • 灵活性:可在组件内部定义(私有)或全局定义(跨组件使用)。
  • 与组件关系@Builder 通常用于 UI 片段的复用,而 @Component 用于定义完整的、带有独立状态和生命周期的自定义组件。

三、基础用法

3.1 组件内 @Builder(私有)

在自定义组件内部定义,仅在该组件内可调用,能够通过 this 访问组件的状态变量。

typescript 复制代码
@Component
struct MyComponent {
  @State count: number = 0;

  @Builder ItemBuilder(text: string) {
    Text(text)
      .padding(10)
      .backgroundColor(0xeeeeee)
  }

  build() {
    Column() {
      this.ItemBuilder("条目1")
      this.ItemBuilder("条目2")
    }
  }
}

3.2 全局 @Builder

在模块级别定义,可在整个应用内调用,但不允许使用 this,因此不适合依赖组件状态的场景。

typescript 复制代码
@Builder
function GlobalBuilder(param: string) {
  Text(param).fontSize(16)
}

@Component
struct MyComponent {
  build() {
    Column() {
      GlobalBuilder("全局构建函数")
    }
  }
}

四、参数传递:按值传递 vs 按引用传递

这是 @Builder 使用中最关键的概念,理解不当会导致 UI 不刷新的问题。

4.1 按值传递(默认)

传递的参数在 @Builder 内部是值的拷贝,外部状态变化不会触发 @Builder 内部的 UI 刷新。

typescript 复制代码
// 按值传递(不响应外部变化)
@Builder
function ValueBuilder(paramA1: string) {
  Row() {
    Text(`Builder - 按值传递: ${paramA1}`)
      .fontSize(20)
  }
}

@Entry
@Component
struct Parent {
  @State label: string = 'Hello';

  build() {
    Column({space: 20}) {
      ValueBuilder(this.label)          // label 变化不会更新

      Divider()

      Text(`label的值:${this.label}`)
        .fontSize(20)

      Button('点击')
        .width('80%')
        .onClick(() => {
        this.label = 'ArkUI'
      })
    }
    .width('100%')
  }
}

运行效果:

4.2 按引用传递(响应外部变化)

使用对象字面量传递参数,当状态变量变化时,@Builder 内部的 UI 会同步刷新。

@Builder 按引用传递的核心规则是:必须且只能传入一个参数,且该参数必须是一个对象字面量。

以下是具体的实现步骤和示例:

  1. 定义一个类或接口,用于封装需要传递的参数。
  2. @Builder 函数中,将参数类型指定为该类或接口。
  3. 在调用 @Builder 时,使用对象字面量 { 属性名: 状态变量 }的形式传入。
typescript 复制代码
// 1. 定义一个类,用于封装参数
class Tmp {
  param: string = '';
}

// 按引用传递(响应外部变化)
// 2. 定义全局 @Builder 函数,参数类型为 Tmp
@Builder
function RefBuilder(tmp: Tmp) {
  Row() {
    Text(`Builder - 按引用传递: ${tmp.param}`)
      .fontSize(20)
  }
}

@Entry
@Component
struct ReferencePassingExample {
  @State label: string = 'Hello';

  build() {
    Column({space: 20}) {
      // 3. 调用时,使用对象字面量形式传入状态变量
      RefBuilder({ param: this.label })

      Divider()

      Text(`label的值:${this.label}`)
        .fontSize(20)

      Button('改变值')
        .width('80%')
        .onClick(() => {
          this.label = 'ArkUI'; // 修改状态变量,会触发 @Builder 内 UI 刷新
        })
    }
    .width('100%')
  }
}

运行效果:

关键点与常见错误:

  1. 必须使用对象字面量:{ param: this.myParam } 这种形式是触发按引用传递的必要条件。
  2. 只能传入一个参数:如果 @Builder 函数需要多个值,必须将它们封装到同一个对象中(如上面的 Tmp 类),不能写成 RefBuilder({ param: this.myParam }, 12)
  3. 不能使用 new 构造对象:RefBuilder(new Tmp(this.myParam)) 这种方式会变成按值传递,UI 不会刷新。
  4. 不能直接传字符串:RefBuilder(this.myParam) 是典型的按值传递,UI 不会刷新。
  5. 禁止修改参数值:在 @Builder 函数内部,不允许修改传入的参数(如 tmp.param = 'new'),否则会抛出运行时错误。
typescript 复制代码
@Builder
function RefBuilder(tmp: Tmp) {
  Row() {
    Text(`Builder - 按引用传递: ${tmp.param}`)
      .fontSize(20)
      .onClick(() => {
        tmp.param = 'new';
      })
  }
}

运行效果:

注意:按值传递时,修改参数值不会报错:

typescript 复制代码
@Builder
function ValueBuilder(paramA1: string) {
  Row() {
    Text(`Builder - 按值传递: ${paramA1}`)
      .fontSize(20)
      .onClick(() => {
        paramA1 = 'World'
        console.log(`修改后的值为:${paramA1}`)
      })
  }
}

运行效果:

五、高级用法与最佳实践

5.1 在 @Builder 中使用循环

结合 ForEach 可以动态生成多个 UI 片段。

typescript 复制代码
@Builder
function ListBuilder(items: string[]) {
  Column() {
    ForEach(items, (item: string) => {
      Text(item)
        .padding(8)
        .width('100%')
    })
  }
}

5.2 嵌套 @Builder

@Builder 函数内部可以调用其他 @Builder 函数,实现更细粒度的复用。

typescript 复制代码
@Builder
function HeaderBuilder(title: string) {
  Text(title)
    .fontSize(20)
    .fontWeight(FontWeight.Bold)
}

@Builder
function CardBuilder(title: string, content: string) {
  Column() {
    HeaderBuilder(title)
    Text(content)
      .fontSize(14)
  }
  .padding(16)
  .backgroundColor(0xffffff)
  .borderRadius(8)
}

在组件内 @Builder 中,可以直接使用 this 访问组件的 @State@Prop@Link 等装饰器修饰的变量。

typescript 复制代码
@Component
struct ChildComponent {
  @Prop message: string;

  @Builder MessageBuilder() {
    Text(this.message)
      .fontColor(Color.Blue)
  }

  build() {
    Column() {
      this.MessageBuilder()
    }
  }
}

六、注意事项

  • 参数传递机制 :若 @Builder 需要响应状态变化,务必使用按引用传递(封装为对象)。直接传递基本类型(如 numberstring)会导致 UI 不更新。
  • 作用域选择 :仅在单个组件内复用的 UI 使用组件内 @Builder;无状态、全局复用的 UI 使用全局 @Builder
  • 避免滥用@Builder 适合静态或简单动态 UI 片段;对于具有独立生命周期或复杂逻辑的 UI,应使用 @Component
  • 参数不可变 :在 @Builder 函数内部不要修改参数值(ArkTS 强制约束),如需双向同步,考虑使用 @Link 装饰器。
  • 性能考量:按引用传递可能引起更多 UI 刷新,合理设计参数结构以避免不必要的重绘。

七、总结

@Builder 是 ArkUI 声明式开发范式中提升 UI 复用性的重要工具。正确理解其参数传递机制(按值 vs 按引用)是避免 UI 更新问题的关键。

在开发中,根据场景灵活选择 @Builder@Component,能够构建出既高效又易维护的 HarmonyOS 应用界面。

相关推荐
AlienZHOU44 分钟前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
Captaincc4 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师5 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen6 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒6 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
贾伟康6 小时前
【HarmonyOS 7新能力|026】Agent Framework Kit工程封装:把接入逻辑放进可维护的分层结构
agent·harmonyos·arkts·a2a·harmonyos 7
前端snow7 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林8187 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang800787 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明7 小时前
第 6 章 组件进阶
前端·vue.js