本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新
在鸿蒙(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. 构建函数使用动词+名词形式,如buildButton
、renderHeader
-
性能优化:
- 复杂UI考虑使用
@BuilderParam
分拆组件 - 频繁更新的UI使用按引用传递
- 复杂UI考虑使用
-
代码组织:
- 相关构建函数集中管理
- 全局构建函数单独文件存放
-
状态管理:
- 避免在构建函数内直接修改参数
- 需要状态联动时使用@Link或按引用传递
-
组合使用:
- 在自定义组件内同时使用@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
})
}
}
}