1# \@BuilderParam装饰器:引用\@Builder函数
2
3
4当开发者创建了自定义组件,并想对该组件添加特定功能,例如想在某一个指定的自定义组件中添加一个点击跳转操作,此时若直接在组件内嵌入事件方法,将会导致所有该自定义组件的实例都增加了功能。为解决此问题,ArkUI引入了\@BuilderParam装饰器,\@BuilderParam用来装饰指向[\@Builder](./arkts-builder.md)方法的变量(@BuilderParam是用来承接@Builder函数的)。开发者可以在初始化自定义组件时,使用不同的方式(如:参数修改、尾随闭包、借用箭头函数等)对\@BuilderParam装饰的自定义构建函数进行传参赋值,在自定义组件内部通过调用\@BuilderParam为组件增加特定的功能。该装饰器用于声明任意UI描述的一个元素,类似slot占位符。
5
6
7在阅读本文档前,建议提前阅读:[\@Builder](./arkts-builder.md)。
8
9> **说明:**
10>
11> 从API version 9开始,该装饰器支持在ArkTS卡片中使用。
12>
13> 从API version 11开始,该装饰器支持在原子化服务中使用。
14
15
16## 装饰器使用说明
17
18
19### 初始化\@BuilderParam装饰的方法
20
21\@BuilderParam装饰的方法只能被自定义构建函数(\@Builder装饰的方法)初始化。
22
23- 使用所属自定义组件的自定义构建函数或者全局的自定义构建函数,在本地初始化\@BuilderParam。
24
25  ```ts
26  @Builder function overBuilder() {}
27
28  @Component
29  struct Child {
30    @Builder doNothingBuilder() {};
31    // 使用自定义组件的自定义构建函数初始化@BuilderParam
32    @BuilderParam customBuilderParam: () => void = this.doNothingBuilder;
33    // 使用全局自定义构建函数初始化@BuilderParam
34    @BuilderParam customOverBuilderParam: () => void = overBuilder;
35    build(){}
36  }
37  ```
38
39- 用父组件自定义构建函数初始化子组件\@BuilderParam装饰的方法。
40
41  ```ts
42  @Component
43  struct Child {
44    @Builder customBuilder() {};
45    @BuilderParam customBuilderParam: () => void = this.customBuilder;
46
47    build() {
48      Column() {
49        this.customBuilderParam()
50      }
51    }
52  }
53
54  @Entry
55  @Component
56  struct Parent {
57    @Builder componentBuilder() {
58      Text(`Parent builder `)
59    }
60
61    build() {
62      Column() {
63        Child({ customBuilderParam: this.componentBuilder })
64      }
65    }
66  }
67  ```
68  **图1** 示例效果图
69
70  ![builderparam-demo1](figures/builderparam-demo1.png)
71
72
73- 需要注意this的指向。
74
75  以下示例对this的指向做了介绍。
76
77    ```ts
78    @Component
79    struct Child {
80      label: string = `Child`;
81      @Builder customBuilder() {};
82      @Builder customChangeThisBuilder() {};
83      @BuilderParam customBuilderParam: () => void = this.customBuilder;
84      @BuilderParam customChangeThisBuilderParam: () => void = this.customChangeThisBuilder;
85
86      build() {
87        Column() {
88          this.customBuilderParam()
89          this.customChangeThisBuilderParam()
90        }
91      }
92    }
93
94    @Entry
95    @Component
96    struct Parent {
97      label: string = `Parent`;
98
99      @Builder componentBuilder() {
100        Text(`${this.label}`)
101      }
102
103      build() {
104        Column() {
105          // 调用this.componentBuilder()时,this指向当前@Entry所装饰的Parent组件,即label变量的值为"Parent"。
106          this.componentBuilder()
107          Child({
108            // 把this.componentBuilder传给子组件Child的@BuilderParam customBuilderParam,this指向的是子组件Child,即label变量的值为"Child"。
109            customBuilderParam: this.componentBuilder,
110            // 把():void=>{this.componentBuilder()}传给子组件Child的@BuilderParam customChangeThisBuilderParam,
111            // 因为箭头函数的this指向的是宿主对象,所以label变量的值为"Parent"。
112            customChangeThisBuilderParam: (): void => { this.componentBuilder() }
113          })
114        }
115      }
116    }
117    ```
118  **图2** 示例效果图
119
120  ![builderparam-demo2](figures/builderparam-demo2.png)
121
122
123## 限制条件
124
125- \@BuilderParam装饰的变量只能使用\@Builder函数来进行初始化。详情见[@BuilderParam装饰器初始化的值必须为@Builder](#builderparam装饰器初始化的值必须为builder)
126
127- 当@Require装饰器和\@BuilderParam装饰器一起使用的时候,\@BuilderParam装饰器必须进行初始化。详情见[@Require装饰器和@BuilderParam装饰器联合使用](#require装饰器和builderparam装饰器联合使用)。
128
129- 在自定义组件尾随闭包的场景下,子组件有且仅有一个\@BuilderParam用来接收此尾随闭包,且此\@BuilderParam不能有参数。详情见[尾随闭包初始化组件](#尾随闭包初始化组件)。
130
131## 使用场景
132
133### 参数初始化组件
134
135\@BuilderParam装饰的方法可以是有参数和无参数的两种形式,需与指向的\@Builder方法类型匹配。
136
137```ts
138class Tmp{
139  label: string = '';
140}
141
142@Builder function overBuilder($$: Tmp) {
143  Text($$.label)
144    .width(400)
145    .height(50)
146    .backgroundColor(Color.Green)
147}
148
149@Component
150struct Child {
151  label: string = 'Child';
152  @Builder customBuilder() {};
153  // 无参数类型,指向的customBuilder也是无参数类型
154  @BuilderParam customBuilderParam: () => void = this.customBuilder;
155  // 有参数类型,指向的overBuilder也是有参数类型的方法
156  @BuilderParam customOverBuilderParam: ($$: Tmp) => void = overBuilder;
157
158  build() {
159    Column() {
160      this.customBuilderParam()
161      this.customOverBuilderParam({label: 'global Builder label' } )
162    }
163  }
164}
165
166@Entry
167@Component
168struct Parent {
169  label: string = 'Parent';
170
171  @Builder componentBuilder() {
172    Text(`${this.label}`)
173  }
174
175  build() {
176    Column() {
177      this.componentBuilder()
178      Child({ customBuilderParam: this.componentBuilder, customOverBuilderParam: overBuilder })
179    }
180  }
181}
182```
183**图3** 示例效果图
184
185![builderparam-demo3](figures/builderparam-demo3.png)
186
187
188### 尾随闭包初始化组件
189
190在自定义组件中使用\@BuilderParam装饰的属性时也可通过尾随闭包进行初始化。在初始化自定义组件时,组件后紧跟一个大括号“{}”形成尾随闭包场景。
191
192> **说明:**
193>
194>  - 此场景下自定义组件内有且仅有一个使用\@BuilderParam装饰的属性。
195>
196>  - 此场景下自定义组件不支持使用通用属性。
197
198开发者可以将尾随闭包内的内容看做\@Builder装饰的函数传给\@BuilderParam。
199
200示例1:
201
202```ts
203@Component
204struct CustomContainer {
205  @Prop header: string = '';
206  @Builder closerBuilder(){};
207  // 使用父组件的尾随闭包{}(@Builder装饰的方法)初始化子组件@BuilderParam
208  @BuilderParam closer: () => void = this.closerBuilder;
209
210  build() {
211    Column() {
212      Text(this.header)
213        .fontSize(30)
214      this.closer()
215    }
216  }
217}
218
219@Builder function specificParam(label1: string, label2: string) {
220  Column() {
221    Text(label1)
222      .fontSize(30)
223    Text(label2)
224      .fontSize(30)
225  }
226}
227
228@Entry
229@Component
230struct CustomContainerUser {
231  @State text: string = 'header';
232
233  build() {
234    Column() {
235      // 创建CustomContainer,在创建CustomContainer时,通过其后紧跟一个大括号“{}”形成尾随闭包
236      // 作为传递给子组件CustomContainer @BuilderParam closer: () => void的参数
237      CustomContainer({ header: this.text }) {
238        Column() {
239          specificParam('testA', 'testB')
240        }.backgroundColor(Color.Yellow)
241        .onClick(() => {
242          this.text = 'changeHeader';
243        })
244      }
245    }
246  }
247}
248```
249**图4** 示例效果图
250
251![builderparam-demo4](figures/builderparam-demo4.png)
252
253使用全局@Builder和局部@Builder通过尾随闭包的形式去初始化@ComponentV2修饰的自定义组件中的@BuilderParam。
254
255示例2:
256
257```ts
258@ComponentV2
259struct ChildPage {
260  @Require @Param message: string = "";
261  @Builder customBuilder() {};
262  @BuilderParam customBuilderParam: () => void = this.customBuilder;
263
264  build() {
265    Column() {
266      Text(this.message)
267        .fontSize(30)
268        .fontWeight(FontWeight.Bold)
269      this.customBuilderParam()
270    }
271  }
272}
273
274const builder_value: string = 'Hello World';
275@Builder function overBuilder() {
276  Row() {
277    Text(`全局 Builder: ${builder_value}`)
278      .fontSize(20)
279      .fontWeight(FontWeight.Bold)
280  }
281}
282
283@Entry
284@ComponentV2
285struct ParentPage {
286  @Local label: string = `Parent Page`;
287
288  @Builder componentBuilder() {
289    Row(){
290      Text(`局部 Builder :${this.label}`)
291        .fontSize(20)
292        .fontWeight(FontWeight.Bold)
293    }
294  }
295
296  build() {
297    Column() {
298      ChildPage({ message: this.label}){
299        Column() {  // 使用局部@Builder,通过组件后紧跟一个大括号“{}”形成尾随闭包去初始化自定义组件@BuilderParam
300          this.componentBuilder();
301        }
302      }
303      Line()
304        .width('100%')
305        .height(10)
306        .backgroundColor('#000000').margin(10)
307      ChildPage({ message: this.label}){  // 使用全局@Builder,通过组件后紧跟一个大括号“{}”形成尾随闭包去初始化自定义组件@BuilderParam
308        Column() {
309          overBuilder();
310        }
311      }
312    }
313  }
314}
315```
316
317### 使用全局和局部\@Builder初始化\@BuilderParam
318
319在自定义组件中,使用\@BuilderParam修饰的变量接收来自父组件通过\@Builder传递的内容进行初始化,因为父组件的\@Builder可以使用箭头函数的形式改变当前的this指向,所以当使用\@BuilderParam修饰的变量时,会展示出不同的内容。
320
321```ts
322@Component
323struct ChildPage {
324  label: string = `Child Page`;
325  @Builder customBuilder() {};
326  @BuilderParam customBuilderParam: () => void = this.customBuilder;
327  @BuilderParam customChangeThisBuilderParam: () => void = this.customBuilder;
328
329  build() {
330    Column() {
331      this.customBuilderParam()
332      this.customChangeThisBuilderParam()
333    }
334  }
335}
336
337const builder_value: string = 'Hello World';
338@Builder function overBuilder() {
339  Row() {
340    Text(`全局 Builder: ${builder_value}`)
341      .fontSize(20)
342      .fontWeight(FontWeight.Bold)
343  }
344}
345
346@Entry
347@Component
348struct ParentPage {
349  label: string = `Parent Page`;
350
351  @Builder componentBuilder() {
352    Row(){
353      Text(`局部 Builder :${this.label}`)
354        .fontSize(20)
355        .fontWeight(FontWeight.Bold)
356    }
357  }
358
359  build() {
360    Column() {
361      // 调用this.componentBuilder()时,this指向当前@Entry所装饰的ParentPage组件,所以label变量的值为"Parent Page"。
362      this.componentBuilder()
363      ChildPage({
364        // 把this.componentBuilder传给子组件ChildPage的@BuilderParam customBuilderParam,this指向的是子组件ChildPage,所以label变量的值为"Child Page"。
365        customBuilderParam: this.componentBuilder,
366        // 把():void=>{this.componentBuilder()}传给子组件ChildPage的@BuilderParam customChangeThisBuilderParam,
367        // 因为箭头函数的this指向的是宿主对象,所以label变量的值为"Parent Page"。
368        customChangeThisBuilderParam: (): void => { this.componentBuilder() }
369      })
370      Line()
371        .width('100%')
372        .height(10)
373        .backgroundColor('#000000').margin(10)
374      // 调用全局overBuilder()时,this指向当前整个活动页,所以展示的内容为"Hello World"。
375      overBuilder()
376      ChildPage({
377        // 把全局overBuilder传给子组件ChildPage的@BuilderParam customBuilderParam,this指向当前整个活动页,所以展示的内容为"Hello World"。
378        customBuilderParam: overBuilder,
379        // 把全局overBuilder传给子组件ChildPage的@BuilderParam customChangeThisBuilderParam,this指向当前整个活动页,所以展示的内容为"Hello World"。
380        customChangeThisBuilderParam: overBuilder
381      })
382    }
383  }
384}
385```
386**图5** 示例效果图
387
388![builderparam-demo5](figures/builderparam-demo5.png)
389
390### 在@ComponentV2修饰的自定义组件中使用@BuilderParam
391
392使用全局@Builder和局部@Builder去初始化@CompoentV2修饰的自定义组件中的@BuilderParam属性。
393
394```ts
395@ComponentV2
396struct ChildPage {
397  @Param label: string = `Child Page`;
398  @Builder customBuilder() {};
399  @BuilderParam customBuilderParam: () => void = this.customBuilder;
400  @BuilderParam customChangeThisBuilderParam: () => void = this.customBuilder;
401
402  build() {
403    Column() {
404      this.customBuilderParam()
405      this.customChangeThisBuilderParam()
406    }
407  }
408}
409
410const builder_value: string = 'Hello World';
411@Builder function overBuilder() {
412  Row() {
413    Text(`全局 Builder: ${builder_value}`)
414      .fontSize(20)
415      .fontWeight(FontWeight.Bold)
416  }
417}
418
419@Entry
420@ComponentV2
421struct ParentPage {
422  @Local label: string = `Parent Page`;
423
424  @Builder componentBuilder() {
425    Row(){
426      Text(`局部 Builder :${this.label}`)
427        .fontSize(20)
428        .fontWeight(FontWeight.Bold)
429    }
430  }
431
432  build() {
433    Column() {
434      // 调用this.componentBuilder()时,this指向当前@Entry所装饰的ParentPage组件,所以label变量的值为"Parent Page"。
435      this.componentBuilder()
436      ChildPage({
437        // 把this.componentBuilder传给子组件ChildPage的@BuilderParam customBuilderParam,this指向的是子组件ChildPage,所以label变量的值为"Child Page"。
438        customBuilderParam: this.componentBuilder,
439        // 把():void=>{this.componentBuilder()}传给子组件ChildPage的@BuilderParam customChangeThisBuilderPara
440        // 因为箭头函数的this指向的是宿主对象,所以label变量的值为"Parent Page"。
441        customChangeThisBuilderParam: (): void => { this.componentBuilder() }
442      })
443      Line()
444        .width('100%')
445        .height(5)
446        .backgroundColor('#000000').margin(10)
447      // 调用全局overBuilder()时,this指向当前整个活动页,所以展示的内容为"Hello World"。
448      overBuilder()
449      ChildPage({
450        // 把全局overBuilder传给子组件ChildPage的@BuilderParam customBuilderParam,this指向当前整个活动页,所以展示的内容为"Hello World"。
451        customBuilderParam: overBuilder,
452        // 把全局overBuilder传给子组件ChildPage的@BuilderParam customChangeThisBuilderParam,this指向当前整个活动页,所以展示的内容为"Hello World"。
453        customChangeThisBuilderParam: overBuilder
454      })
455    }
456  }
457}
458```
459**图6** 示例效果图
460
461![builderparam-demo6](figures/builderparam-demo6.png)
462
463
464## 常见问题
465
466### 改变内容UI不刷新
467
468当调用自定义组件ChildPage时,把\@Builder作为参数通过this.componentBuilder的形式传递,当前this会指向自定义组件内部,所以在父组件里面改变label的值,自定义组件ChildPage是感知不到的。
469
470【反例】
471
472```ts
473@Component
474struct ChildPage {
475  @State label: string = `Child Page`;
476  @Builder customBuilder() {};
477  @BuilderParam customChangeThisBuilderParam: () => void = this.customBuilder;
478
479  build() {
480    Column() {
481      this.customChangeThisBuilderParam()
482    }
483  }
484}
485
486@Entry
487@Component
488struct ParentPage {
489  @State label: string = `Parent Page`;
490
491  @Builder componentBuilder() {
492    Row(){
493      Text(`Builder :${this.label}`)
494        .fontSize(20)
495        .fontWeight(FontWeight.Bold)
496    }
497  }
498
499  build() {
500    Column() {
501      ChildPage({
502        // 当前写法this指向ChildPage组件内
503        customChangeThisBuilderParam: this.componentBuilder
504      })
505      Button('点击改变label内容')
506        .onClick(() => {
507          this.label = 'Hello World';
508        })
509    }
510  }
511}
512```
513
514使用箭头函数的形式把\@Builder传递进自定义组件ChildPage中,当前this指向会停留在父组件ParentPage里,所以在父组件里改变label的值,自定义组件ChildPage会感知到并重新渲染UI。
515把@Builder改为@LocalBuilder也能实现动态渲染UI功能。
516
517【正例】
518
519```ts
520@Component
521struct ChildPage {
522  @State label: string = `Child Page`;
523  @Builder customBuilder() {};
524  @BuilderParam customChangeThisBuilderParam: () => void = this.customBuilder;
525
526  build() {
527    Column() {
528      this.customChangeThisBuilderParam()
529    }
530  }
531}
532
533@Entry
534@Component
535struct ParentPage {
536  @State label: string = `Parent Page`;
537
538  @Builder componentBuilder() {
539    Row(){
540      Text(`Builder :${this.label}`)
541        .fontSize(20)
542        .fontWeight(FontWeight.Bold)
543    }
544  }
545
546  build() {
547    Column() {
548      ChildPage({
549        customChangeThisBuilderParam: () => { this.componentBuilder() }
550      })
551      Button('点击改变label内容')
552        .onClick(() => {
553          this.label = 'Hello World';
554        })
555    }
556  }
557}
558```
559
560### @Require装饰器和@BuilderParam装饰器联合使用
561
562由于@Require装饰器所装饰的变量需进行初始化,若变量未初始化,在编译时会输出报错信息。
563
564【反例】
565
566```ts
567@Builder function globalBuilder() {
568  Text('Hello World')
569}
570
571@Entry
572@Component
573struct customBuilderDemo {
574  build() {
575    Column() {
576      // 由于未对子组件ChildBuilder进行赋值,此处无论是编译还是编辑,均会报错。
577      ChildPage()
578    }
579  }
580}
581
582@Component
583struct ChildPage {
584  @Require @BuilderParam ChildBuilder: () => void = globalBuilder;
585  build() {
586    Column() {
587      this.ChildBuilder()
588    }
589  }
590}
591```
592
593对使用@Require装饰器修饰的变量进行初始化,此时,编译不会报错,无报错信息。
594
595【正例】
596
597```ts
598@Builder function globalBuilder() {
599  Text('Hello World')
600}
601
602@Entry
603@Component
604struct customBuilderDemo {
605  build() {
606    Column() {
607      ChildPage({ChildBuilder: globalBuilder})
608    }
609  }
610}
611
612@Component
613struct ChildPage {
614  @Require @BuilderParam ChildBuilder: () => void = globalBuilder;
615  build() {
616    Column() {
617      this.ChildBuilder()
618    }
619  }
620}
621```
622
623### @BuilderParam装饰器初始化的值必须为@Builder
624
625使用@State装饰器修饰的变量,给子组件@BuilderParam和ChildBuilder变量进行初始化,在编译时会输出报错信息。
626
627【反例】
628
629```ts
630@Builder function globalBuilder() {
631  Text('Hello World')
632}
633@Entry
634@Component
635struct customBuilderDemo {
636  @State message: string = "";
637  build() {
638    Column() {
639      // 子组件ChildBuilder接收@State修饰的变量,会出现编译和编辑报错
640      ChildPage({ChildBuilder: this.message})
641    }
642  }
643}
644
645@Component
646struct ChildPage {
647  @BuilderParam ChildBuilder: () => void = globalBuilder;
648  build() {
649    Column() {
650      this.ChildBuilder()
651    }
652  }
653}
654```
655
656使用全局的@Builder修饰的globalBuilder()给子组件@BuilderParam修饰的ChildBuilder变量进行初始化,编译时没有报错,功能正常。
657
658【正例】
659
660```ts
661@Builder function globalBuilder() {
662  Text('Hello World')
663}
664@Entry
665@Component
666struct customBuilderDemo {
667  build() {
668    Column() {
669      ChildPage({ChildBuilder: globalBuilder})
670    }
671  }
672}
673
674@Component
675struct ChildPage {
676  @BuilderParam ChildBuilder: () => void = globalBuilder;
677  build() {
678    Column() {
679      this.ChildBuilder()
680    }
681  }
682}
683```