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