以下是鸿蒙 ArkUI 中 @Builder
、@BuilderParam
和 wrapBuilder
的详细解析与代码案例,涵盖核心概念、使用场景及最佳实践:
⚙️ 一、@Builder
:轻量级 UI 复用
1. 私有构建函数
定义在组件内部,通过 this
访问组件状态,适合组件内重复 UI 结构复用:
ts
@Entry
@Component
struct PrivateBuilderDemo {
@State count: number = 0;
// 私有构建函数
@Builder
CounterButton() {
Button(`点击: ${this.count}`)
.onClick(() => this.count++) // 直接访问组件状态
}
build() {
Column() {
this.CounterButton() // 调用
this.CounterButton() // 复用
}
}
}
特点:
- 通过
this
访问组件状态(如@State
) - 仅在当前组件内可用
2. 全局构建函数
独立于组件定义,可跨组件复用,无法访问组件状态:
ts
// 全局定义
@Builder
function GlobalButton(text: string) {
Button(text)
.width(200)
.backgroundColor(Color.Blue)
}
@Entry
@Component
struct GlobalDemo {
build() {
Column() {
GlobalButton("提交") // 跨组件调用
GlobalButton("取消")
}
}
}
特点:
- 通过函数名直接调用
- 适合无状态 UI(如通用按钮、卡片)
3. 参数传递规则
-
值传递(默认) :
状态更新不会触发 UI 刷新(传递副本):
ts@Builder function ValueDemo(param: string) { Text(param) // 值拷贝 }
-
引用传递 :
使用对象字面量 传递,状态更新触发 UI 刷新:
tsclass Config { content: string = "" } @Builder function ReferenceDemo($$: Config) { Text($$.content) // 引用传递 } @Entry @Component struct Parent { @State config = new Config(); build() { Column() { ReferenceDemo({ content: this.config.content }) // 对象字面量 Button("更新").onClick(() => this.config.content = "New") } } }
关键点:
- 引用传递需满足:单参数 + 对象字面量
- 命名约定:推荐用
$$
标识引用参数
🧩 二、@BuilderParam
:动态 UI 插槽
用于在自定义组件中声明 UI 占位符,允许父组件注入 UI 结构:
typescript
// 子组件
@Component
struct ChildComponent {
@BuilderParam customUI: () => void // 声明占位符
build() {
Column() {
Text("固定内容")
this.customUI() // 渲染父组件传入的UI
}
}
}
// 父组件
@Entry
@Component
struct ParentComponent {
@Builder
parentBuilder() {
Row() {
Text("父组件传入的内容")
Image($r('app.media.icon'))
}
}
build() {
Column() {
ChildComponent({ customUI: this.parentBuilder }) // 注入UI
}
}
}
高级用法:支持带参数的构建函数
ts
@Component
struct ParamChild {
@BuilderParam paramBuilder: (color: Color) => void
build() {
Column() {
this.paramBuilder(Color.Red) // 传入参数
}
}
}
@Entry
@Component
struct ParamParent {
@Builder
colorBlock($$: { color: Color }) {
Text("文本").fontColor($$.color)
}
build() {
ParamChild({ paramBuilder: this.colorBlock })
}
}
特点:
- 类似 Vue 的插槽机制(
slot
) - 通过箭头函数保留父组件状态上下文
📦 三、wrapBuilder
:全局构建器的动态封装
解决全局 @Builder
无法存入数组或动态赋值 的问题,返回 WrappedBuilder
对象:
ts
// 定义全局构建器
@Builder
function GlobalText(value: string, size: number) {
Text(value).fontSize(size)
}
// 封装为可存储对象
const wrappedText: WrappedBuilder<[string, number]> = wrapBuilder(GlobalText);
@Entry
@Component
struct WrapDemo {
@State message: string = "Hello";
build() {
Column() {
wrappedText.builder(this.message, 30) // 调用封装后的构建器
}
}
}
动态管理构建器数组:
ts
@Builder
function BuilderA() { Text("A") }
@Builder
function BuilderB() { Text("B") }
// 存入数组
const builderArr: WrappedBuilder<[]>[] = [
wrapBuilder(BuilderA),
wrapBuilder(BuilderB)
];
@Entry
@Component
struct ArrayDemo {
build() {
Column() {
ForEach(builderArr, (item) => {
item.builder() // 遍历调用
})
}
}
}
限制:
- 仅支持全局
@Builder
- 不可重复赋值(需通过中间对象更新)
🔍 四、三者的核心差异对比
特性 | @Builder |
@BuilderParam |
wrapBuilder |
---|---|---|---|
作用域 | 组件内/全局 | 组件内部 | 全局构建器 |
核心用途 | 封装可复用 UI 片段 | 声明 UI 插槽占位符 | 全局构建器的动态管理 |
状态访问 | 私有构建器可访问组件状态 | 通过箭头函数继承父组件状态 | 需显式传递参数 |
动态化支持 | ❌ 不可存入数组 | ❌ 依赖父组件初始化 | ✅ 可存入数组、变量传递 |
⚠️ 五、避坑指南
-
**
@Builder
更新失效
错误:多参数或非对象字面量传递状态变量。
解决**:封装为单对象参数 。 -
**
@BuilderParam
丢失this
错误:Child({ builder: this.parentBuilder })
(指向子组件)。
修正**:Child({ builder: () => this.parentBuilder() })
。 -
**
wrapBuilder
无法更新
错误:直接赋值this.wrappedBuilder = wrapBuilder(New)
。
解决**:通过中间对象更新:ini@State builderWrapper = { current: wrapBuilder(OldBuilder) }; // 更新时 this.builderWrapper = { current: wrapBuilder(NewBuilder) };
通过组合使用这三种机制,可构建高灵活、低耦合的鸿蒙 UI 架构:
- 基础复用 :
@Builder
封装按钮/卡片等原子组件 - 动态插槽 :
@BuilderParam
实现可配置弹窗/表格 - 高阶抽象 :
wrapBuilder
管理主题库或动态仪表盘