• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# \@Builder装饰器:自定义构建函数
2
3ArkUI提供了一种轻量的UI元素复用机制\@Builder,该自定义组件内部UI结构固定,仅与使用方进行数据传递,开发者可以将重复使用的UI元素抽象成一个方法,在build方法里调用。
4
5为了简化语言,我们将\@Builder装饰的函数也称为“自定义构建函数”。
6
7
8> **说明:**
9>
10> 从API version 9开始,该装饰器支持在ArkTS卡片中使用。
11>
12> 从API version 11开始,该装饰器支持在原子化服务中使用。
13
14## 限制条件
15
16- \@Builder通过按引用传递的方式传入参数,才会触发动态渲染UI,并且参数只能是一个。
17
18- \@Builder如果传入的参数是两个或两个以上,不会触发动态渲染UI。
19
20- \@Builder传入的参数中同时包含按值传递和按引用传递两种方式,不会触发动态渲染UI。
21
22- \@Builder的参数必须按照对象字面量的形式,把所需要的属性一一传入,才会触发动态渲染UI。
23
24## 装饰器使用说明
25
26### 私有自定义构建函数
27
28定义的语法:
29
30```ts
31@Builder MyBuilderFunction() {}
32```
33
34使用方法:
35
36```ts
37this.MyBuilderFunction()
38```
39
40- 允许在自定义组件内定义一个或多个@Builder方法,该方法被认为是该组件的私有、特殊类型的成员函数。
41
42- 私有自定义构建函数允许在自定义组件内、build方法和其他自定义构建函数中调用。
43
44- 在自定义函数体中,this指代当前所属组件,组件的状态变量可以在自定义构建函数内访问。建议通过this访问自定义组件的状态变量而不是参数传递。
45
46
47### 全局自定义构建函数
48
49定义的语法:
50
51```ts
52@Builder function MyGlobalBuilderFunction() { ... }
53```
54
55使用方法:
56
57```ts
58MyGlobalBuilderFunction()
59```
60
61- 如果不涉及组件状态变化,建议使用全局的自定义构建方法。
62
63- 全局自定义构建函数允许在build方法和其他自定义构建函数中调用。
64
65
66## 参数传递规则
67
68自定义构建函数的参数传递有[按值传递](#按值传递参数)和[按引用传递](#按引用传递参数)两种,均需遵守以下规则:
69
70- 参数的类型必须与参数声明的类型一致,不允许undefined、null和返回undefined、null的表达式。
71
72- 在@Builder修饰的函数内部,不允许改变参数值。
73
74- \@Builder内UI语法遵循[UI语法规则](arkts-create-custom-components.md#build函数)。
75
76- 只有传入一个参数,且参数需要直接传入对象字面量才会按引用传递该参数,其余传递方式均为按值传递。
77
78
79### 按引用传递参数
80
81按引用传递参数时,传递的参数可为状态变量,且状态变量的改变会引起\@Builder方法内的UI刷新。
82
83```ts
84class Tmp {
85  paramA1: string = ''
86}
87
88@Builder function overBuilder(params: Tmp) {
89  Row() {
90    Text(`UseStateVarByReference: ${params.paramA1} `)
91  }
92}
93@Entry
94@Component
95struct Parent {
96  @State label: string = 'Hello';
97  build() {
98    Column() {
99      // 在父组件中调用overBuilder组件时,
100      // 把this.label通过引用传递的方式传给overBuilder组件。
101      overBuilder({ paramA1: this.label })
102      Button('Click me').onClick(() => {
103        // 单击Click me后,UI文本从Hello更改为ArkUI。
104        this.label = 'ArkUI';
105      })
106    }
107  }
108}
109```
110
111按引用传递参数时,如果在\@Builder方法内调用自定义组件,ArkUI提供[$$](arkts-two-way-sync.md)作为按引用传递参数的范式。
112
113```ts
114class Tmp {
115  paramA1: string = ''
116}
117
118@Builder function overBuilder($$: Tmp) {
119  Row() {
120    Column() {
121      Text(`overBuilder===${$$.paramA1}`)
122      HelloComponent({message: $$.paramA1})
123    }
124  }
125}
126
127@Component
128struct HelloComponent {
129  @Prop message: string;
130
131  build() {
132    Row() {
133      Text(`HelloComponent===${this.message}`)
134    }
135  }
136}
137
138@Entry
139@Component
140struct Parent {
141  @State label: string = 'Hello';
142  build() {
143    Column() {
144      // 在父组件中调用overBuilder组件时,
145      // 把this.label通过引用传递的方式传给overBuilder组件。
146      overBuilder({paramA1: this.label})
147      Button('Click me').onClick(() => {
148        // 单击Click me后,UI文本从Hello更改为ArkUI。
149        this.label = 'ArkUI';
150      })
151    }
152  }
153}
154```
155
156### 按值传递参数
157
158调用\@Builder装饰的函数默认按值传递。当传递的参数为状态变量时,状态变量的改变不会引起\@Builder方法内的UI刷新。所以当使用状态变量的时候,推荐使用[按引用传递](#按引用传递参数)。
159
160```ts
161@Builder function overBuilder(paramA1: string) {
162  Row() {
163    Text(`UseStateVarByValue: ${paramA1} `)
164  }
165}
166@Entry
167@Component
168struct Parent {
169  @State label: string = 'Hello';
170  build() {
171    Column() {
172      overBuilder(this.label)
173    }
174  }
175}
176```
177
178使用按值传递的方式,在@ComponentV2装饰器修饰的自定义组件里配合使用@ObservedV2和@Trace装饰器可以实现刷新UI功能。
179
180【正例】
181
182在@ComponentV2装饰中,只有使用@ObservedV2修饰的ParamTmp类和@Trace修饰的count属性才可以触发UI的刷新。
183
184```ts
185@ObservedV2
186class ParamTmp {
187  @Trace count : number = 0;
188}
189
190@Builder
191function renderText(param: ParamTmp) {
192  Column() {
193    Text(`param : ${param.count}`)
194      .fontSize(20)
195      .fontWeight(FontWeight.Bold)
196  }
197}
198
199@Builder
200function renderMap(paramMap: Map<string,number>) {
201  Text(`paramMap : ${paramMap.get('name')}`)
202    .fontSize(20)
203    .fontWeight(FontWeight.Bold)
204}
205
206@Builder
207function renderSet(paramSet: Set<number>) {
208  Text(`paramSet : ${paramSet.size}`)
209    .fontSize(20)
210    .fontWeight(FontWeight.Bold)
211}
212
213@Builder
214function renderNumberArr(paramNumArr: number[]) {
215  Text(`paramNumArr : ${paramNumArr[0]}`)
216    .fontSize(20)
217    .fontWeight(FontWeight.Bold)
218}
219
220@Entry
221@ComponentV2
222struct PageBuilder {
223  @Local builderParams: ParamTmp = new ParamTmp();
224  @Local map_value: Map<string,number> = new Map();
225  @Local set_value: Set<number> = new Set([0]);
226  @Local numArr_value: number[] = [0];
227  private progressTimer: number = -1;
228
229  aboutToAppear(): void {
230    this.progressTimer = setInterval(() => {
231      if (this.builderParams.count < 100) {
232        this.builderParams.count += 5;
233        this.map_value.set('name', this.builderParams.count);
234        this.set_value.add(this.builderParams.count);
235        this.numArr_value[0] = this.builderParams.count;
236      } else {
237        clearInterval(this.progressTimer)
238      }
239    }, 500);
240  }
241
242  @Builder
243  localBuilder() {
244    Column() {
245      Text(`localBuilder : ${this.builderParams.count}`)
246        .fontSize(20)
247        .fontWeight(FontWeight.Bold)
248    }
249  }
250
251  build() {
252    Column() {
253      this.localBuilder()
254      Text(`builderParams :${this.builderParams.count}`)
255        .fontSize(20)
256        .fontWeight(FontWeight.Bold)
257      renderText(this.builderParams)
258      renderText({ count: this.builderParams.count })
259      renderMap(this.map_value)
260      renderSet(this.set_value)
261      renderNumberArr(this.numArr_value)
262    }
263    .width('100%')
264    .height('100%')
265  }
266}
267```
268
269【反例】
270
271在@ComponentV2装饰的自定义组件中,使用简单数据类型不可以触发UI的刷新。
272
273```ts
274@ObservedV2
275class ParamTmp {
276  @Trace count : number = 0;
277}
278
279@Builder
280function renderNumber(paramNum: number) {
281  Text(`paramNum : ${paramNum}`)
282    .fontSize(30)
283    .fontWeight(FontWeight.Bold)
284}
285
286@Entry
287@ComponentV2
288struct PageBuilder {
289  @Local class_value: ParamTmp = new ParamTmp();
290  // 此处使用简单数据类型不支持刷新UI的能力。
291  @Local num_value: number = 0;
292  private progressTimer: number = -1;
293
294  aboutToAppear(): void {
295    this.progressTimer = setInterval(() => {
296      if (this.class_value.count < 100) {
297        this.class_value.count += 5;
298        this.num_value += 5;
299      } else {
300        clearInterval(this.progressTimer)
301      }
302    }, 500);
303  }
304
305  build() {
306    Column() {
307      renderNumber(this.num_value)
308    }
309    .width('100%')
310    .height('100%')
311    .padding(50)
312  }
313}
314```
315
316## 使用场景
317
318### 自定义组件内使用自定义构建函数
319
320创建私有的\@Builder方法,在Column里面使用this.builder()方式调用,通过aboutToAppear生命周期函数和按钮的点击事件改变builder_value的内容,实现动态渲染UI。
321
322```ts
323@Entry
324@Component
325struct PrivateBuilder {
326  @State builder_value: string = 'Hello';
327
328  @Builder builder() {
329    Column(){
330      Text(this.builder_value)
331        .fontSize(30)
332        .fontWeight(FontWeight.Bold)
333    }
334  }
335
336  aboutToAppear(): void {
337    setTimeout(() => {
338      this.builder_value = 'Hello World';
339    },3000)
340  }
341
342  build() {
343    Row() {
344      Column() {
345        Text(this.builder_value)
346          .fontSize(30)
347          .fontWeight(FontWeight.Bold)
348        this.builder()
349        Button('点击改变builder_value内容')
350          .onClick(() => {
351            this.builder_value ='builder_value被点击了'
352          })
353      }
354    }
355  }
356}
357```
358
359### 使用全局自定义构建函数
360
361创建全局的\@Builder方法,在Column里面使用overBuilder()方式调用,通过以对象字面量的形式传递参数,无论是简单类型还是复杂类型,值的改变都会引起UI界面的刷新。
362
363```ts
364class ChildTmp {
365  val: number = 1;
366}
367
368class Tmp {
369  str_value: string = 'Hello';
370  num_value: number = 0;
371  tmp_value: ChildTmp = new ChildTmp();
372  arrayTmp_value: Array<ChildTmp> = [];
373}
374
375@Builder function overBuilder(param: Tmp) {
376  Column() {
377    Text(`str_value: ${param.str_value}`)
378    Text(`num_value: ${param.num_value}`)
379    Text(`tmp_value: ${param.tmp_value.val}`)
380    ForEach(param.arrayTmp_value, (item: ChildTmp) => {
381      Text(`arrayTmp_value: ${item.val}`)
382    }, (item: ChildTmp) => JSON.stringify(item))
383  }
384}
385
386@Entry
387@Component
388struct Parent {
389  @State objParam: Tmp = new Tmp();
390  build() {
391    Column() {
392      Text('通过调用@Builder渲染UI界面')
393        .fontSize(20)
394      overBuilder({str_value: this.objParam.str_value, num_value: this.objParam.num_value,
395       tmp_value: this.objParam.tmp_value, arrayTmp_value: this.objParam.arrayTmp_value})
396      Line()
397        .width('100%')
398        .height(10)
399        .backgroundColor('#000000').margin(10)
400      Button('点击改变参数值').onClick(() => {
401        this.objParam.str_value = 'Hello World';
402        this.objParam.num_value = 1;
403        this.objParam.tmp_value.val = 8;
404        const child_value: ChildTmp = {
405          val: 2
406        }
407        this.objParam.arrayTmp_value.push(child_value)
408      })
409    }
410  }
411}
412```
413
414### 修改装饰器修饰的变量触发UI刷新
415
416此种方式是使用了装饰器的特性,监听值的改变触发UI刷新,不通过\@Builder传递参数。
417
418```ts
419class Tmp {
420  str_value: string = 'Hello';
421}
422
423@Entry
424@Component
425struct Parent {
426  @State objParam: Tmp = new Tmp();
427  @State label: string = 'World';
428
429  @Builder privateBuilder() {
430    Column() {
431      Text(`wrapBuilder str_value: ${this.objParam.str_value}`)
432      Text(`wrapBuilder num: ${this.label}`)
433    }
434  }
435
436  build() {
437    Column() {
438      Text('通过调用@Builder渲染UI界面')
439        .fontSize(20)
440      this.privateBuilder()
441      Line()
442        .width('100%')
443        .height(10)
444        .backgroundColor('#000000').margin(10)
445      Button('点击改变参数值').onClick(() => {
446        this.objParam.str_value = 'str_value Hello World';
447        this.label = 'label Hello World'
448      })
449    }
450  }
451}
452```
453
454### 使用全局和局部的@Builder传入customBuilder类型
455
456```ts
457@Builder
458function overBuilder() {
459  Row() {
460    Text('全局 Builder')
461      .fontSize(30)
462      .fontWeight(FontWeight.Bold)
463  }
464}
465
466@Entry
467@Component
468struct customBuilderDemo {
469  @State arr: number[] = [0, 1, 2, 3, 4];
470
471  @Builder privateBuilder() {
472    Row() {
473      Text('局部 Builder')
474        .fontSize(30)
475        .fontWeight(FontWeight.Bold)
476    }
477  }
478
479  build() {
480    Column() {
481      List({ space: 10 }) {
482        ForEach(this.arr, (item: number) => {
483          ListItem(){
484            Text(`${item}`)
485              .width('100%')
486              .height(100)
487              .fontSize(16)
488              .textAlign(TextAlign.Center)
489              .borderRadius(10)
490              .backgroundColor(0xFFFFFF)
491          }
492            .swipeAction({
493              start: {
494                builder: overBuilder()
495              },
496              end: {
497                builder: () => { this.privateBuilder() }
498              }
499            })
500        }, (item: string) => JSON.stringify(item))
501      }
502    }
503  }
504}
505```
506
507### 多层\@Builder方法嵌套使用
508
509在\@Builder方法内调用自定义组件或者其他\@Builder方法,ArkUI提供[$$](arkts-two-way-sync.md)作为按引用传递参数的范式。
510
511```ts
512class Tmp {
513  paramA1: string = '';
514}
515
516@Builder function parentBuilder($$: Tmp) {
517  Row() {
518    Column() {
519      Text(`parentBuilder===${$$.paramA1}`)
520        .fontSize(30)
521        .fontWeight(FontWeight.Bold)
522      HelloComponent({message: $$.paramA1})
523      childBuilder({paramA1: $$.paramA1})
524    }
525  }
526}
527
528@Component
529struct HelloComponent {
530  @Prop message: string = '';
531
532  build() {
533    Row() {
534      Text(`HelloComponent===${this.message}`)
535        .fontSize(30)
536        .fontWeight(FontWeight.Bold)
537    }
538  }
539}
540
541@Builder
542function childBuilder($$: Tmp) {
543  Row() {
544    Column() {
545      Text(`childBuilder===${$$.paramA1}`)
546        .fontSize(30)
547        .fontWeight(FontWeight.Bold)
548      HelloChildComponent({message: $$.paramA1})
549      grandsonBuilder({paramA1: $$.paramA1})
550    }
551  }
552}
553
554@Component
555struct HelloChildComponent {
556  @Prop message: string = '';
557  build() {
558    Row() {
559      Text(`HelloChildComponent===${this.message}`)
560        .fontSize(30)
561        .fontWeight(FontWeight.Bold)
562    }
563  }
564}
565
566@Builder function grandsonBuilder($$: Tmp) {
567  Row() {
568    Column() {
569      Text(`grandsonBuilder===${$$.paramA1}`)
570        .fontSize(30)
571        .fontWeight(FontWeight.Bold)
572      HelloGrandsonComponent({message: $$.paramA1})
573    }
574  }
575}
576
577@Component
578struct HelloGrandsonComponent {
579  @Prop message: string;
580  build() {
581    Row() {
582      Text(`HelloGrandsonComponent===${this.message}`)
583        .fontSize(30)
584        .fontWeight(FontWeight.Bold)
585    }
586  }
587}
588
589@Entry
590@Component
591struct Parent {
592  @State label: string = 'Hello';
593  build() {
594    Column() {
595      parentBuilder({paramA1: this.label})
596      Button('Click me').onClick(() => {
597        this.label = 'ArkUI';
598      })
599    }
600  }
601}
602```
603
604### \@Builder函数联合V2装饰器使用
605
606使用全局@Builder和局部@Builder在@ComponentV2修饰的自定义组件中调用,修改相关变量触发UI刷新。
607
608```ts
609@ObservedV2
610class Info {
611  @Trace name: string = '';
612  @Trace age: number = 0;
613}
614
615@Builder
616function overBuilder(param: Info) {
617  Column() {
618    Text(`全局@Builder name :${param.name}`)
619      .fontSize(30)
620      .fontWeight(FontWeight.Bold)
621    Text(`全局@Builder age :${param.age}`)
622      .fontSize(30)
623      .fontWeight(FontWeight.Bold)
624  }
625}
626
627@ComponentV2
628struct ChildPage {
629  @Require @Param childInfo: Info;
630  build() {
631    overBuilder({name: this.childInfo.name, age: this.childInfo.age})
632  }
633}
634
635@Entry
636@ComponentV2
637struct ParentPage {
638  info1: Info = { name: "Tom", age: 25 };
639  @Local info2: Info = { name: "Tom", age: 25 };
640
641  @Builder
642  privateBuilder() {
643    Column() {
644      Text(`局部@Builder name :${this.info1.name}`)
645        .fontSize(30)
646        .fontWeight(FontWeight.Bold)
647      Text(`局部@Builder age :${this.info1.age}`)
648        .fontSize(30)
649        .fontWeight(FontWeight.Bold)
650    }
651  }
652
653  build() {
654    Column() {
655      Text(`info1: ${this.info1.name}  ${this.info1.age}`) // Text1
656        .fontSize(30)
657        .fontWeight(FontWeight.Bold)
658      this.privateBuilder() // 调用局部@Builder
659      Line()
660        .width('100%')
661        .height(10)
662        .backgroundColor('#000000').margin(10)
663      Text(`info2: ${this.info2.name}  ${this.info2.age}`) // Text2
664        .fontSize(30)
665        .fontWeight(FontWeight.Bold)
666      overBuilder({ name: this.info2.name, age: this.info2.age}) // 调用全局@Builder
667      Line()
668        .width('100%')
669        .height(10)
670        .backgroundColor('#000000').margin(10)
671      Text(`info1: ${this.info1.name}  ${this.info1.age}`) // Text1
672        .fontSize(30)
673        .fontWeight(FontWeight.Bold)
674      ChildPage({ childInfo: this.info1}) // 调用自定义组件
675      Line()
676        .width('100%')
677        .height(10)
678        .backgroundColor('#000000').margin(10)
679      Text(`info2: ${this.info2.name}  ${this.info2.age}`) // Text2
680        .fontSize(30)
681        .fontWeight(FontWeight.Bold)
682      ChildPage({ childInfo: this.info2}) // 调用自定义组件
683      Line()
684        .width('100%')
685        .height(10)
686        .backgroundColor('#000000').margin(10)
687      Button("change info1&info2")
688        .onClick(() => {
689          this.info1 = { name: "Cat", age: 18} // Text1不会刷新,原因是没有装饰器修饰监听不到值的改变。
690          this.info2 = { name: "Cat", age: 18} // Text2会刷新,原因是有装饰器修饰,可以监听到值的改变。
691        })
692    }
693  }
694}
695```
696
697## 常见问题
698
699### \@Builder存在两个或者两个以上参数
700
701当参数存在两个或者两个以上的时候,就算通过对象字面量的形式传递,值的改变也不会引起UI刷新。
702
703【反例】
704
705```ts
706class GlobalTmp {
707  str_value: string = 'Hello';
708}
709
710@Builder function overBuilder(param: GlobalTmp, num: number) {
711  Column() {
712    Text(`str_value: ${param.str_value}`)
713    Text(`num: ${num}`)
714  }
715}
716
717@Entry
718@Component
719struct Parent {
720  @State objParam: GlobalTmp = new GlobalTmp();
721  @State num: number = 0;
722  build() {
723    Column() {
724      Text('通过调用@Builder渲染UI界面')
725        .fontSize(20)
726      // 使用了两个参数,用法错误。
727      overBuilder({str_value: this.objParam.str_value}, this.num)
728      Line()
729        .width('100%')
730        .height(10)
731        .backgroundColor('#000000').margin(10)
732      Button('点击改变参数值').onClick(() => {
733        this.objParam.str_value = 'Hello World';
734        this.num = 1;
735      })
736    }
737  }
738}
739```
740
741【反例】
742
743```ts
744class GlobalTmp {
745  str_value: string = 'Hello';
746}
747class SecondTmp {
748  num_value: number = 0;
749}
750@Builder function overBuilder(param: GlobalTmp, num: SecondTmp) {
751  Column() {
752    Text(`str_value: ${param.str_value}`)
753    Text(`num: ${num.num_value}`)
754  }
755}
756
757@Entry
758@Component
759struct Parent {
760  @State strParam: GlobalTmp = new GlobalTmp();
761  @State numParam: SecondTmp = new SecondTmp();
762  build() {
763    Column() {
764      Text('通过调用@Builder渲染UI界面')
765        .fontSize(20)
766      // 使用了两个参数,用法错误。
767      overBuilder({str_value: this.strParam.str_value}, {num_value: this.numParam.num_value})
768      Line()
769        .width('100%')
770        .height(10)
771        .backgroundColor('#000000').margin(10)
772      Button('点击改变参数值').onClick(() => {
773        this.strParam.str_value = 'Hello World';
774        this.numParam.num_value = 1;
775      })
776    }
777  }
778}
779```
780
781\@Builder只接受一个参数,当传入一个参数的时候,通过对象字面量的形式传递,值的改变会引起UI的刷新。
782
783【正例】
784
785```ts
786class GlobalTmp {
787  str_value: string = 'Hello';
788  num_value: number = 0;
789}
790@Builder function overBuilder(param: GlobalTmp) {
791  Column() {
792    Text(`str_value: ${param.str_value}`)
793    Text(`num: ${param.num_value}`)
794  }
795}
796
797@Entry
798@Component
799struct Parent {
800  @State objParam: GlobalTmp = new GlobalTmp();
801  build() {
802    Column() {
803      Text('通过调用@Builder渲染UI界面')
804        .fontSize(20)
805      overBuilder({str_value: this.objParam.str_value, num_value: this.objParam.num_value})
806      Line()
807        .width('100%')
808        .height(10)
809        .backgroundColor('#000000').margin(10)
810      Button('点击改变参数值').onClick(() => {
811        this.objParam.str_value = 'Hello World';
812        this.objParam.num_value = 1;
813      })
814    }
815  }
816}
817```
818