背景
开发者创建自定义组件时,常需为组件添加如点击跳转等特定功能。但直接在组件内嵌入事件方法,会使所有使用该组件之处都具备此功能,缺乏灵活性。例如,一个通用的按钮组件,若内置特定点击跳转,在不同页面使用时无法按需定制跳转地址。
ArkUI 的解决方案:@BuilderParam 装饰器
- 装饰作用:@BuilderParam 用于修饰指向 @Builder 方法的变量,成为 @Builder 函数的承接者。@Builder 方法用于构建 UI 元素或描述 UI 行为逻辑,@BuilderParam 让开发者能灵活控制这些构建逻辑在组件中的应用时机与方式。
- 传参赋值机制:初始化自定义组件阶段,开发者可借助参数修改、尾随闭包、借用箭头函数等多样方式为被 @BuilderParam 装饰的自定义构建函数传递参数并赋值。以参数修改为例,假设组件用于显示不同文本,可在初始化时将文本内容作为参数传给 @BuilderParam 装饰的函数,从而改变组件显示文本;尾随闭包方式则允许在组件初始化末尾,通过闭包传递复杂逻辑或额外数据;借用箭头函数能简洁地传入动态计算的参数或函数引用。
- 功能实现路径:在组件内部调用 @BuilderParam,以此为组件动态增添特定功能。如点击跳转场景,组件内点击事件触发时调用 @BuilderParam,执行外部传入的构建函数实现跳转逻辑定制,不同使用场景传入不同跳转函数,满足多样化需求。
类似 slot 占位符的特性
如同 slot 占位符,@BuilderParam 声明 UI 描述元素,预留位置与功能扩展空间。开发者在组件使用处填充内容或逻辑,像 slot 允许父组件向子组件插入内容,@BuilderParam 使外部逻辑注入组件,增强组件复用性与功能扩展性,提升开发效率与代码维护性,构建更灵活、可定制的 UI 组件体系。
下面是使用@BuilderParam需要注意的点
1.使用@BuilderParam装饰器在子组件需要添加默认@Builder
以下是错误示范,不添加默认@Builder
@Entry
@Component
struct father {
@Builder
header() {
Text('header')
.width('100%')
.height(50)
.backgroundColor(Color.Gray)
}
@Builder
body() {
Text('body')
.width('100%')
.height(50)
.backgroundColor(Color.Pink)
}
build() {
Column() {
son() {
this.header()
this.body()
}
}
}
}
@Component
struct son {
// @Builder
// default() {
// }
@BuilderParam
builderParam: () => void //在这里需要添加默认@Builder的参数
build() {
Column() {
this.builderParam()
}
}
}
假设不添加默认@Builder会出现预览器报错,预览器不显示的问题
添加默认@Builder
@Entry
@Component
struct father {
@Builder
header() {
Text('header')
.width('100%')
.height(50)
.backgroundColor(Color.Gray)
}
@Builder
body() {
Text('body')
.width('100%')
.height(50)
.backgroundColor(Color.Pink)
}
build() {
Column() {
son() {
this.header()
this.body()
}
}
}
}
@Component
struct son {
@Builder
default() {
}
@BuilderParam
builderParam: () => void = this.default
build() {
Column() {
this.builderParam()
}
}
}
这个时候呢预览器就会正确显示
使用@BuilderParam,是可以使用尾随闭包写法的,但是子组件只允许出现一个@BuilderParam装饰器,不然就会报错,为什么会报错呢,因为有两个@BuilderParam装饰器,父组件尾随闭包写法时,就不知道该传递的是哪一个@BuilderParam修饰的参数
错误写法报错
子组件有两个@BuilderParam参数
@Component
struct son {
@Builder
default() {
}
@BuilderParam
builderParam: () => void = this.default
@BuilderParam
builderParam2: () => void = this.default
build() {
Column() {
this.builderParam()
this.builderParam2()
}
}
}
父组件尾随闭包写法
@Entry
@Component
struct father {
@Builder
header() {
Text('header')
.width('100%')
.height(50)
.backgroundColor(Color.Gray)
}
@Builder
body() {
Text('body')
.width('100%')
.height(50)
.backgroundColor(Color.Pink)
}
build() {
Column() {
son() {
this.header()
this.body()
}
}
}
}
报错,不允许尾随闭包写法需要正常父传子写法。
正常父传子写法,就不会报错,预览器正常显示
build() {
Column() {
son({ builderParam: this.header, builderParam2: this.body })
}
}
}
关于使用@BuilderParam的this的指向性问题
在父组件和子组件同时使用相同变量名,在给@BuilderParam传递Builder,this是指向父组件和子组件的那个变量呢?首先我们得出结论,this指向子组件变量,因为this指向调用者,牢记this指向调用者
@Entry
@Component
struct father {
@State
name: string = '父组件'
@Builder
header() {
Text(this.name)
.width('100%')
.height(50)
.backgroundColor(Color.Gray)
}
@Builder
body() {
Text(this.name)
.width('100%')
.height(50)
.backgroundColor(Color.Pink)
}
build() {
Column() {
son({ builderParam: this.header, builderParam2: this.body })
}
}
}
@Component
struct son {
@State name: string = '子组件'
@Builder
default() {
}
@BuilderParam
builderParam: () => void = this.default
@BuilderParam
builderParam2: () => void = this.default
build() {
Column() {
this.builderParam() //这里才调用
this.builderParam2() //这里才调用
}
}
}
this指向子组件
我就想this指向父组件该怎么办呢
我们可以使用回调函数将this指向父组件,以下是示例
@Entry
@Component
struct father {
@State
name: string = '父组件'
@Builder
header() {
Text(this.name)
.width('100%')
.height(50)
.backgroundColor(Color.Gray)
}
@Builder
body() {
Text(this.name)
.width('100%')
.height(50)
.backgroundColor(Color.Pink)
}
build() {
Column() {
son({
builderParam: () => { //回调函数将this指向改变为父组件
this.header()
}, builderParam2: () => {
this.body()
}
})
}
}
}
总结
当你子组件的参数只有一个BuildrParam才可以使用尾随闭包写法,如果有两个就必须放置builder在大括号里面,没有调用,这个时候会有一个this的指向问题,但是要牢记this指向调用者(子组件),可以使用箭头函数改变this的指向问题,箭头函数的this指向当前组件(父组件),你可能会有疑问,builderParam不是只允许传递builder吗,但是使用箭头函数里面直接调用builder也是属于传递builder(鸿蒙语法是允许的)。