HarmonyOS 开发必会 5 种 Builder 详解
2026年3月3日 22:55
HarmonyOS 开发必会 5 种 Builder 详解,建议收藏
万少:华为HDE、鸿蒙极客
个人主页:blog.zbztb.cn/
2025年参与孵化了20+鸿蒙应用、技术文章300+、鸿蒙知识库用户500+、鸿蒙免费课程2套。
如果你也喜欢交流AI和鸿蒙技术,欢迎扣我。
前言
HarmonyOS应用开发中提供了众多的Builder用于实现结构复用,其中有**@Builder**、@LocalBuilder、@BuilderParam、
wrapBuilder、mutableBuilde等。
熟练掌握以上的Builder使用更加有利于开发出高质量的HarmonyOS应用。
@Builder装饰器
@Builder装饰器提供了最轻量级的组件结构复用,它不想自定义组件具有自己的生命周期和内部状态,所以运行起来体验和性能要更好,当需要在组件中实现结构复用时,优先使用它!
@ComponentV2
@Entry
struct Index {
build() {
Column({ space: 10 }) {
this.MenuItem($r("sys.symbol.position"), "位置")
this.MenuItem($r("sys.symbol.message"), "信息")
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
.padding(10)
}
/**
* @Builder 装饰器:用于定义一个轻量级的 UI 复用单元
* 与 @Component 相比,@Builder 更适合简单的、可复用的 UI 片段
* 该方法可以在 build() 方法中通过 this.MenuItem() 直接调用
*
* @param src - 图标资源,使用 Resource 类型支持系统符号或本地资源
* @param text - 菜单项显示的文本内容
*/
@Builder
MenuItem(src: Resource, text: string) {
Row() {
SymbolGlyph(src) // 系统符号图标
Text(text) // 显示的文本
}
}
}
效果如图所示。
![]()
@BuilderParam装饰器
@BuilderParam 用于声明一个UI 插槽,让父组件可以向子组件传递自定义的 UI 内容。
/**
* 自定义组件 MenuItemChild
* 演示 @BuilderParam 的使用 - 用于接收父组件传递的 UI 结构(类似于 Vue 的 slot 插槽)
*/
@ComponentV2
struct MenuItemChild {
/**
* @BuilderParam 装饰器:用于声明一个"插槽"属性,接收外部传入的 UI 构建函数
* - 允许父组件向子组件传递自定义 UI 内容
* - = this.defaultBuilder 表示设置默认值,当父组件不传递内容时使用
* - 类型 () => void 表示一个无参数无返回值的构建函数
*/
@BuilderParam
slotBuilder: () => void = this.defaultBuilder
/**
* @Builder 装饰器:定义一个轻量级的 UI 构建函数
* - 这里的 defaultBuilder 作为 @BuilderParam 的默认值
* - 当父组件不传递 slot 内容时,显示此默认 UI
*/
@Builder
defaultBuilder() {
// 默认结构:当父组件未传入自定义内容时显示
Button("默认结构")
}
/**
* build 方法:组件的 UI 构建入口
* - 直接调用 slotBuilder() 来渲染传入或默认的 UI 内容
*/
build() {
this.slotBuilder()
}
}
/**
* 主页面组件
* 演示 @BuilderParam 的使用方式:
* 1. 传入自定义内容:MenuItemChild() { Button("父组件") } - 尾随闭包语法
* 2. 不传入内容:MenuItemChild() - 使用默认的 defaultBuilder
*
* @Entry 装饰器:标记此组件为应用入口页面
* @ComponentV2 装饰器:标记此结构体为 UI 组件(V2 版本,性能更优)
*/
@ComponentV2
@Entry
struct Index {
build() {
Column({ space: 10 }) {
// 方式1:通过尾随闭包向子组件传递自定义 UI(@BuilderParam 接收)
MenuItemChild() {
Button("父组件")
}
// 方式2:不传递内容,子组件使用 @BuilderParam 的默认值
MenuItemChild()
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
.padding(10)
}
}
效果如图。
![]()
@LocalBuilder装饰器
@LocalBuilder的作用和**@Builder**类似,都可以实现组件内的组件复用。但是两者存在区别
传递给子组件的参数中的 this 指向:
- @Builder:指向子组件(this 上下文改变)
- @LocalBuilder:指向父组件(this 上下文保持不变)
/**
* 子组件:演示 @BuilderParam 接收外部传入的 UI 构建函数
*/
@ComponentV2
struct MenuItemChild {
/**
* @Local 装饰器:声明组件内的本地状态变量(V2 状态管理)
*/
@Local title: string = "子组件"
/**
* @BuilderParam 装饰器:声明一个 UI 插槽属性
* - 用于接收父组件传递的 @Builder 或 @LocalBuilder 函数
* - 必须由父组件提供值(无默认值时)
*/
@BuilderParam
slotBuilder: () => void
build() {
this.slotBuilder()
}
}
@ComponentV2
@Entry
struct Index {
@Local title: string = "父组件"
@Builder
MenuItem1() {
Button(this.title) // 传递时捕获 title 的当前值
}
@LocalBuilder
MenuItem2() {
Button(this.title) // 始终引用最新的 title 值
}
build() {
Column({ space: 10 }) {
// 使用 @Builder this.MenuItem1 中的 this 指向的是 MenuItemChild组件
MenuItemChild({ slotBuilder: this.MenuItem1 })
// 使用 @LocalBuilder this.MenuItem2 中的 this 指向的是 当前的Index组件
MenuItemChild({ slotBuilder: this.MenuItem2 })
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
.padding(10)
}
}
效果如图。
![]()
wrapBuilder
@Builder存在一个缺陷:当@Builder方法赋值给变量或者数组后,在UI方法中无法使用。
如以下代码:
@Builder
function btn() {
Button("按钮")
}
const builderArr: Function[] = [btn]
@ComponentV2
@Entry
struct Index {
build() {
Column({ space: 10 }) {
ForEach(builderArr, (item: Function) => {
item()// 错误:当@Builder方法赋值给变量或者数组后,在UI方法中无法使用。
})
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
}
}
wrapBuilder便是来解决这个问题的:
-
- 动态存储多个 @Builder 供运行时选择使用
-
- 将 @Builder 作为参数传递给其他函数/组件
-
- 在循环、数组等场景中复用 @Builder
/**
* 数据源:用于 ForEach 循环渲染时传递参数
*/
const titles = ["红牛", "康师傅", "小肥羊"]
/**
* @Builder 函数:定义一个带参数的 UI 构建函数
* @param text - 按钮显示的文本
*/
@Builder
function btn(text: string) {
Button(text)
}
/**
* wrapBuilder 函数详解
* ─────────────────────────────────────────────────────────────────
* 【作用】将全局 @Builder 函数包装成可复用的 WrappedBuilder 对象
*
* 【为什么需要 wrapBuilder?】
* - @Builder 函数本身不能直接赋值给变量、不能放入数组、不能作为参数传递
* - wrapBuilder 将其转换为普通的 JavaScript 对象,支持以上操作
*
* 【语法】
* let 变量名: WrappedBuilder<[参数类型]> = wrapBuilder(builder函数名)
*
* 【本例用法】
* - 将 wrapBuilder 返回的对象放入数组中存储
* - 数组类型:WrappedBuilder<[string]>[] 表示存储多个包装后的 builder
* - 每个元素都可以通过 .builder(参数) 调用原始的 @Builder
*
*
* 【注意】
* - 只能包装全局 @Builder,不能包装组件内的 @Builder
* - 参数类型必须与 @Builder 函数签名完全匹配
* ─────────────────────────────────────────────────────────────────
*/
let globalBuilder: WrappedBuilder<[string]>[] = [wrapBuilder(btn), wrapBuilder(btn)]; //可以放入数组
@ComponentV2
@Entry
struct Index {
build() {
Column({ space: 10 }) {
/**
* 遍历 globalBuilder 数组,调用每个 WrappedBuilder
* - item: WrappedBuilder<[string]> - 包装后的 builder 对象
* - item.builder(titles[index]) - 调用原始 @Builder,传入对应参数
*
* 执行流程:
* index=0 → item.builder("红牛") → 渲染 Button("红牛")
* index=1 → item.builder("康师傅") → 渲染 Button("康师傅")
*/
ForEach(globalBuilder, (item: WrappedBuilder<[string]>, index: number) => {
item.builder(titles[index])
})
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
}
}
效果如图。
![]()
mutableBuilder
当状态变量是wrapBuilder类型时,状态变量发生改变,UI不会跟随改变。此时可以使用mutableBuilder代替wrapBuilder。
wrapBuilder的缺陷:
@Builder
function textBuilder(p: string) {
Text(p)
}
@Builder
function buttonBuilder(p: string) {
Button(p)
}
@Entry
@ComponentV2
struct Index {
@Local message: string = 'init';
@Local text: WrappedBuilder<[string]> = wrapBuilder(textBuilder);
build() {
Column() {
this.text.builder(this.message) // 预期发生更新,但是实际没有更新
Button("改变").onClick(() => {
this.text = wrapBuilder(buttonBuilder); // 点击Button, 页面不会发生更新
})
}
}
}
![]()
将WrappedBuilder替换为mutableBuilder
@Local text: MutableBuilder<[string]> = mutableBuilder(textBuilder);
完整代码:
@Builder
function textBuilder(p: string) {
Text(p)
}
@Builder
function buttonBuilder(p: string) {
Button(p)
}
@Entry
@ComponentV2
struct Index {
@Local message: string = 'init';
@Local text: MutableBuilder<[string]> = mutableBuilder(textBuilder);
build() {
Column({ space: 10 }) {
this.text.builder(this.message) // 预期发生更新,但是实际没有更新
Button("改变").onClick(() => {
this.text = wrapBuilder(buttonBuilder); // 点击Button, 页面不会发生更新
})
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
}
}
![]()
总结
Builder 类型对比表
| Builder 类型 | 核心作用 | this 指向 | 是否可赋值给变量/数组 | 响应式更新 | 典型使用场景 |
|---|---|---|---|---|---|
| @Builder | 轻量级 UI 复用单元 | 调用处的组件 | ❌ 不支持 | ✅ 支持 | 组件内简单 UI 结构复用 |
| @BuilderParam | UI 插槽声明 | 子组件自身 | ❌ 不支持 | ✅ 支持 | 父组件向子组件传递自定义 UI 内容 |
| @LocalBuilder | 组件内 UI 复用(保持 this 上下文) | 定义处的组件 | ❌ 不支持 | ✅ 支持 | 需要保持原始 this 上下文的 UI 复用 |
| wrapBuilder | 包装 @Builder 为可传递对象 | 包装后的对象 | ✅ 支持 | ❌ 不支持 | 动态存储、数组循环、参数传递 |
| mutableBuilder | 可响应式更新的包装器 | 包装后的对象 | ✅ 支持 | ✅ 支持 | 需要响应式更新的动态 Builder 切换 |
最后
关注我,持续分享鸿蒙开发 + AI 提效的实战技巧。