# \@BuilderParam装饰器:引用\@Builder函数 当开发者创建自定义组件并需要为其添加特定功能(例如:点击跳转操作)时,如果直接在组件内嵌入事件方法,会导致所有该自定义组件的实例都增加此功能。为了解决组件功能定制化的问题,ArkUI引入了@BuilderParam装饰器。@BuilderParam用于装饰指向@Builder方法的变量,开发者可以在初始化自定义组件时,使用不同的方式(例如:参数修改、尾随闭包、借用箭头函数等)对@BuilderParam装饰的自定义构建函数进行传参赋值。在自定义组件内部,通过调用@BuilderParam为组件增加特定功能。该装饰器用于声明任意UI描述的元素,类似于slot占位符。 在阅读本文档前,建议提前阅读:[\@Builder](./arkts-builder.md)。 > **说明:** > > 从API version 9开始,该装饰器支持在ArkTS卡片中使用。 > > 从API version 11开始,该装饰器支持在原子化服务中使用。 ## 装饰器使用说明 ### 初始化\@BuilderParam装饰的方法 \@BuilderParam装饰的方法只能被自定义构建函数(\@Builder装饰的方法)初始化。 - 使用所属自定义组件的自定义构建函数或者全局的自定义构建函数,在本地初始化\@BuilderParam。 ```ts @Builder function overBuilder() { } @Component struct Child { @Builder doNothingBuilder() { } // 使用自定义组件的自定义构建函数初始化@BuilderParam @BuilderParam customBuilderParam: () => void = this.doNothingBuilder; // 使用全局自定义构建函数初始化@BuilderParam @BuilderParam customOverBuilderParam: () => void = overBuilder; build() { } } ``` - 用父组件自定义构建函数初始化子组件\@BuilderParam装饰的方法。 ```ts @Component struct Child { @Builder customBuilder() { } @BuilderParam customBuilderParam: () => void = this.customBuilder; build() { Column() { this.customBuilderParam() } } } @Entry @Component struct Parent { @Builder componentBuilder() { Text(`Parent builder `) } build() { Column() { Child({ customBuilderParam: this.componentBuilder }) } } } ``` **图1** 示例效果图 ![builderparam-demo1](figures/builderparam-demo1.png) - 需要注意this的指向。 this指向示例如下: ```ts @Component struct Child { label: string = 'Child'; @Builder customBuilder() { } @Builder customChangeThisBuilder() { } @BuilderParam customBuilderParam: () => void = this.customBuilder; @BuilderParam customChangeThisBuilderParam: () => void = this.customChangeThisBuilder; build() { Column() { this.customBuilderParam() this.customChangeThisBuilderParam() } } } @Entry @Component struct Parent { label: string = 'Parent'; @Builder componentBuilder() { Text(`${this.label}`) } build() { Column() { // 调用this.componentBuilder()时,this指向当前@Entry所装饰的Parent组件,即label变量的值为'Parent'。 this.componentBuilder() Child({ // 把this.componentBuilder传给子组件Child的@BuilderParam customBuilderParam,this指向的是子组件Child,即label变量的值为'Child'。 customBuilderParam: this.componentBuilder, // 把():void=>{this.componentBuilder()}传给子组件Child的@BuilderParam customChangeThisBuilderParam, // 因为箭头函数的this指向的是宿主对象,所以label变量的值为'Parent'。 customChangeThisBuilderParam: (): void => { this.componentBuilder() } }) } } } ``` **图2** 示例效果图 ![builderparam-demo2](figures/builderparam-demo2.png) ## 限制条件 - 使用`@BuilderParam`装饰的变量只能通过`@Builder`函数进行初始化。具体参见[@BuilderParam装饰器初始化的值必须为@Builder](#builderparam装饰器初始化的值必须为builder)。 - 当@Require装饰器和@BuilderParam装饰器一起使用时,@BuilderParam装饰器必须进行初始化。具体请参见[@Require装饰器和@BuilderParam装饰器联合使用](#require装饰器和builderparam装饰器联合使用)。 - 在自定义组件尾随闭包的场景下,子组件有且仅有一个\@BuilderParam用来接收此尾随闭包,且此\@BuilderParam装饰的方法不能有参数。详情见[尾随闭包初始化组件](#尾随闭包初始化组件)。 ## 使用场景 ### 参数初始化组件 `@BuilderParam`装饰的方法为有参数或无参数两种形式,需与指向的`@Builder`方法类型匹配。 ```ts class Tmp { label: string = ''; } @Builder function overBuilder($$: Tmp) { Text($$.label) .width(400) .height(50) .backgroundColor(Color.Green) } @Component struct Child { label: string = 'Child'; @Builder customBuilder() { } // 无参数类型,指向的customBuilder也是无参数类型 @BuilderParam customBuilderParam: () => void = this.customBuilder; // 有参数类型,指向的overBuilder也是有参数类型的方法 @BuilderParam customOverBuilderParam: ($$: Tmp) => void = overBuilder; build() { Column() { this.customBuilderParam() this.customOverBuilderParam({ label: 'global Builder label' }) } } } @Entry @Component struct Parent { label: string = 'Parent'; @Builder componentBuilder() { Text(`${this.label}`) } build() { Column() { this.componentBuilder() Child({ customBuilderParam: this.componentBuilder, customOverBuilderParam: overBuilder }) } } } ``` **图3** 示例效果图 ![builderparam-demo3](figures/builderparam-demo3.png) ### 尾随闭包初始化组件 在自定义组件中,使用\@BuilderParam装饰的属性可通过尾随闭包进行初始化。初始化时,组件后需紧跟一个大括号“{}”形成尾随闭包场景。 > **说明:** > > - 此场景下自定义组件内仅有一个使用\@BuilderParam装饰的属性。 > > - 此场景下自定义组件不支持通用属性。 开发者可以将尾随闭包内的内容看作\@Builder装饰的函数传给\@BuilderParam。 示例1: ```ts @Component struct CustomContainer { @Prop header: string = ''; @Builder closerBuilder() { } // 使用父组件的尾随闭包{}(@Builder装饰的方法)初始化子组件@BuilderParam @BuilderParam closer: () => void = this.closerBuilder; build() { Column() { Text(this.header) .fontSize(30) this.closer() } } } @Builder function specificParam(label1: string, label2: string) { Column() { Text(label1) .fontSize(30) Text(label2) .fontSize(30) } } @Entry @Component struct CustomContainerUser { @State text: string = 'header'; build() { Column() { // 创建CustomContainer,在创建CustomContainer时,通过其后紧跟一个大括号“{}”形成尾随闭包 // 作为传递给子组件CustomContainer @BuilderParam closer: () => void的参数 CustomContainer({ header: this.text }) { Column() { specificParam('testA', 'testB') }.backgroundColor(Color.Yellow) .onClick(() => { this.text = 'changeHeader'; }) } } } } ``` **图4** 示例效果图 ![builderparam-demo4](figures/builderparam-demo4.png) 使用全局`@Builder`和局部`@Builder`通过尾随闭包的形式对`@ComponentV2`装饰的自定义组件中的`@BuilderParam`进行初始化。 示例2: ```ts @ComponentV2 struct ChildPage { @Require @Param message: string = ''; @Builder customBuilder() { } @BuilderParam customBuilderParam: () => void = this.customBuilder; build() { Column() { Text(this.message) .fontSize(30) .fontWeight(FontWeight.Bold) this.customBuilderParam() } } } const builder_value: string = 'Hello World'; @Builder function overBuilder() { Row() { Text(`全局 Builder: ${builder_value}`) .fontSize(20) .fontWeight(FontWeight.Bold) } } @Entry @ComponentV2 struct ParentPage { @Local label: string = 'Parent Page'; @Builder componentBuilder() { Row() { Text(`局部 Builder :${this.label}`) .fontSize(20) .fontWeight(FontWeight.Bold) } } build() { Column() { ChildPage({ message: this.label }) { Column() { // 使用局部@Builder,通过组件后紧跟一个大括号“{}”形成尾随闭包去初始化自定义组件@BuilderParam this.componentBuilder(); } } Line() .width('100%') .height(10) .backgroundColor('#000000').margin(10) ChildPage({ message: this.label }) { // 使用全局@Builder,通过组件后紧跟一个大括号“{}”形成尾随闭包去初始化自定义组件@BuilderParam Column() { overBuilder(); } } } } } ``` ### 使用\@BuilderParam隔离多组件对\@Builder跳转逻辑的调用 当@Builder封装的系统组件包含跳转逻辑时,所有调用该@Builder的自定义组件将具备该跳转功能。对于需要禁用跳转的特定组件,可使用@BuilderParam来隔离跳转逻辑。 > **说明:** > > 当前示例代码中使用了Navigation组件导航,具体实现逻辑可以查询[Navigation](../arkts-navigation-navigation.md)指南。 ```ts import { HelloWorldPageBuilder } from './helloworld'; class navigationParams { pathStack: NavPathStack = new NavPathStack(); boo: boolean = true; } @Builder function navigationAction(params: navigationParams) { Column() { Navigation(params.pathStack) { Button('router to page', { stateEffect: true, type: ButtonType.Capsule }) .width('80%') .height(40) .margin(20) .onClick(() => { // 通过修改@BuilderParam参数决定是否跳转。 if (params.boo) { params.pathStack.pushPath({ name: 'HelloWorldPage' }); } else { console.info('@BuilderParam setting does not jump'); } }) } .navDestination(HelloWorldPageBuilder) .hideTitleBar(true) .height('100%') .width('100%') } .height('25%') .width('100%') } @Entry @Component struct ParentPage { @State info: navigationParams = new navigationParams(); build() { Column() { Text('ParentPage') navigationAction({ pathStack: this.info.pathStack, boo: true }) ChildPageOne() ChildPage_BuilderParam({ eventBuilder: navigationAction }) } .height('100%') .width('100%') } } @Component struct ChildPageOne { @State info: navigationParams = new navigationParams(); build() { Column() { Text('ChildPage') navigationAction({ pathStack: this.info.pathStack, boo: true }) } } } @Component struct ChildPage_BuilderParam { @State info: navigationParams = new navigationParams(); @BuilderParam eventBuilder: (param: navigationParams) => void = navigationAction; build() { Column() { Text('ChildPage_BuilderParam') // 对传递过来的全局@Builder进行参数修改,可以实现禁用点击跳转的功能。 this.eventBuilder({ pathStack: this.info.pathStack, boo: false }) } } } ``` ```ts // helloworld.ets @Builder export function HelloWorldPageBuilder() { HelloWorldPage() } @Component struct HelloWorldPage { @State message: string = 'Hello World'; @State pathStack: NavPathStack = new NavPathStack(); build() { NavDestination() { Column() { Text(this.message) .fontSize(20) .fontWeight(FontWeight.Bold) } } .height('100%') .width('100%') } } ``` ```ts // router_map.json { "routerMap": [ { "name": "HelloWorldPage", "buildFunction": "HelloWorldPageBuilder", "pageSourceFile": "src/main/ets/pages/helloworld.ets" } ] } ``` ```ts // module.json5 { "module": { "routerMap": "$profile:router_map", ...... } } ``` **图5** 示例效果图 ![builderparam-demo7](figures/builderparam-demo7.gif) ### 使用全局和局部\@Builder初始化\@BuilderParam 在自定义组件中,使用\@BuilderParam装饰的变量接收父组件通过\@Builder传递的内容进行初始化,由于父组件的\@Builder可以使用箭头函数改变当前的this指向,因此使用\@BuilderParam装饰的变量会展示不同的内容。 ```ts @Component struct ChildPage { label: string = 'Child Page'; @Builder customBuilder() { } @BuilderParam customBuilderParam: () => void = this.customBuilder; @BuilderParam customChangeThisBuilderParam: () => void = this.customBuilder; build() { Column() { this.customBuilderParam() this.customChangeThisBuilderParam() } } } const builder_value: string = 'Hello World'; @Builder function overBuilder() { Row() { Text(`全局 Builder: ${builder_value}`) .fontSize(20) .fontWeight(FontWeight.Bold) } } @Entry @Component struct ParentPage { label: string = 'Parent Page'; @Builder componentBuilder() { Row() { Text(`局部 Builder :${this.label}`) .fontSize(20) .fontWeight(FontWeight.Bold) } } build() { Column() { // 调用this.componentBuilder()时,this指向当前@Entry所装饰的ParentPage组件,所以label变量的值为'Parent Page'。 this.componentBuilder() ChildPage({ // 把this.componentBuilder传给子组件ChildPage的@BuilderParam customBuilderParam,this指向的是子组件ChildPage,所以label变量的值为'Child Page'。 customBuilderParam: this.componentBuilder, // 把():void=>{this.componentBuilder()}传给子组件ChildPage的@BuilderParam customChangeThisBuilderParam, // 因为箭头函数的this指向的是宿主对象,所以label变量的值为'Parent Page'。 customChangeThisBuilderParam: (): void => { this.componentBuilder() } }) Line() .width('100%') .height(10) .backgroundColor('#000000').margin(10) // 调用全局overBuilder()时,this指向当前整个活动页,所以展示的内容为'Hello World'。 overBuilder() ChildPage({ // 把全局overBuilder传给子组件ChildPage的@BuilderParam customBuilderParam,this指向当前整个活动页,所以展示的内容为'Hello World'。 customBuilderParam: overBuilder, // 把全局overBuilder传给子组件ChildPage的@BuilderParam customChangeThisBuilderParam,this指向当前整个活动页,所以展示的内容为'Hello World'。 customChangeThisBuilderParam: overBuilder }) } } } ``` **图6** 示例效果图 ![builderparam-demo5](figures/builderparam-demo5.png) ### 在@ComponentV2装饰的自定义组件中使用@BuilderParam 使用全局@Builder和局部@Builder初始化@ComponentV2装饰的自定义组件中的@BuilderParam属性。 ```ts @ComponentV2 struct ChildPage { @Param label: string = 'Child Page'; @Builder customBuilder() { } @BuilderParam customBuilderParam: () => void = this.customBuilder; @BuilderParam customChangeThisBuilderParam: () => void = this.customBuilder; build() { Column() { this.customBuilderParam() this.customChangeThisBuilderParam() } } } const builder_value: string = 'Hello World'; @Builder function overBuilder() { Row() { Text(`全局 Builder: ${builder_value}`) .fontSize(20) .fontWeight(FontWeight.Bold) } } @Entry @ComponentV2 struct ParentPage { @Local label: string = 'Parent Page'; @Builder componentBuilder() { Row() { Text(`局部 Builder :${this.label}`) .fontSize(20) .fontWeight(FontWeight.Bold) } } build() { Column() { // 调用this.componentBuilder()时,this指向当前@Entry所装饰的ParentPage组件,所以label变量的值为'Parent Page'。 this.componentBuilder() ChildPage({ // 把this.componentBuilder传给子组件ChildPage的@BuilderParam customBuilderParam,this指向的是子组件ChildPage,所以label变量的值为'Child Page'。 customBuilderParam: this.componentBuilder, // 把():void=>{this.componentBuilder()}传给子组件ChildPage的@BuilderParam customChangeThisBuilderPara // 因为箭头函数的this指向的是宿主对象,所以label变量的值为'Parent Page'。 customChangeThisBuilderParam: (): void => { this.componentBuilder() } }) Line() .width('100%') .height(5) .backgroundColor('#000000').margin(10) // 调用全局overBuilder()时,this指向当前整个活动页,所以展示的内容为'Hello World'。 overBuilder() ChildPage({ // 把全局overBuilder传给子组件ChildPage的@BuilderParam customBuilderParam,this指向当前整个活动页,所以展示的内容为'Hello World'。 customBuilderParam: overBuilder, // 把全局overBuilder传给子组件ChildPage的@BuilderParam customChangeThisBuilderParam,this指向当前整个活动页,所以展示的内容为'Hello World'。 customChangeThisBuilderParam: overBuilder }) } } } ``` **图7** 示例效果图 ![builderparam-demo6](figures/builderparam-demo6.png) ## 常见问题 ### 改变内容UI不刷新 调用自定义组件ChildPage时,通过`this.componentBuilder`形式传递`@Builder`参数。由于`this`指向自定义组件内部,因此在父组件中改变`label`的值时,自定义组件ChildPage无法感知到这一变化。 【反例】 ```ts @Component struct ChildPage { @State label: string = 'Child Page'; @Builder customBuilder() { } @BuilderParam customChangeThisBuilderParam: () => void = this.customBuilder; build() { Column() { this.customChangeThisBuilderParam() } } } @Entry @Component struct ParentPage { @State label: string = 'Parent Page'; @Builder componentBuilder() { Row() { Text(`Builder :${this.label}`) .fontSize(20) .fontWeight(FontWeight.Bold) } } build() { Column() { ChildPage({ // 当前写法this指向ChildPage组件内 customChangeThisBuilderParam: this.componentBuilder }) Button('点击改变label内容') .onClick(() => { this.label = 'Hello World'; }) } } } ``` 使用箭头函数将`@Builder`传递到自定义组件`ChildPage`中,这样`this`指向会停留在父组件`ParentPage`里。因此,在父组件中改变`label`的值时,`ChildPage`会感知到并重新渲染UI。 【正例】 ```ts @Component struct ChildPage { @State label: string = 'Child Page'; @Builder customBuilder() { } @BuilderParam customChangeThisBuilderParam: () => void = this.customBuilder; build() { Column() { this.customChangeThisBuilderParam() } } } @Entry @Component struct ParentPage { @State label: string = 'Parent Page'; @Builder componentBuilder() { Row() { Text(`Builder :${this.label}`) .fontSize(20) .fontWeight(FontWeight.Bold) } } build() { Column() { ChildPage({ customChangeThisBuilderParam: () => { this.componentBuilder() } }) Button('点击改变label内容') .onClick(() => { this.label = 'Hello World'; }) } } } ``` ### @Require装饰器和@BuilderParam装饰器联合使用 由于@Require装饰器所装饰的变量需进行初始化,若变量未初始化,在编译时会输出报错信息。 【反例】 ```ts @Builder function globalBuilder() { Text('Hello World') } @Entry @Component struct CustomBuilderDemo { build() { Column() { // 由于未对子组件ChildBuilder进行赋值,此处无论是编译还是编辑,均会报错。 ChildPage() } } } @Component struct ChildPage { @Require @BuilderParam ChildBuilder: () => void = globalBuilder; build() { Column() { this.ChildBuilder() } } } ``` 对@Require装饰的变量进行外部传入初始化。 【正例】 ```ts @Builder function globalBuilder() { Text('Hello World') } @Entry @Component struct CustomBuilderDemo { build() { Column() { ChildPage({ ChildBuilder: globalBuilder }) } } } @Component struct ChildPage { @Require @BuilderParam ChildBuilder: () => void = globalBuilder; build() { Column() { this.ChildBuilder() } } } ``` ### @BuilderParam装饰器初始化的值必须为@Builder 使用`@State`装饰器装饰的变量,给子组件的`@BuilderParam`和`ChildBuilder`变量初始化时,编译时会输出报错信息。 【反例】 ```ts @Builder function globalBuilder() { Text('Hello World') } @Entry @Component struct CustomBuilderDemo { @State message: string = ''; build() { Column() { // 子组件ChildBuilder接收@State装饰的变量,会出现编译和编辑报错 ChildPage({ ChildBuilder: this.message }) } } } @Component struct ChildPage { @BuilderParam ChildBuilder: () => void = globalBuilder; build() { Column() { this.ChildBuilder() } } } ``` 使用全局`@Builder`装饰的`globalBuilder()`方法为子组件`@BuilderParam`装饰的`ChildBuilder`变量进行初始化,编译时无报错,功能正常。 【正例】 ```ts @Builder function globalBuilder() { Text('Hello World') } @Entry @Component struct CustomBuilderDemo { build() { Column() { ChildPage({ChildBuilder: globalBuilder}) } } } @Component struct ChildPage { @BuilderParam ChildBuilder: () => void = globalBuilder; build() { Column() { this.ChildBuilder() } } } ```