• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# \@Builder Decorator: Custom Builder Function
2
3ArkUI provides the \@Builder decorator that is a lightweight UI element reuse mechanism. This decorator has a fixed internal UI structure and passes the data only to the user. You can abstract reused UI elements into a method and call the method in the **build** method.
4
5For simplicity, here we refer to an \@Builder decorated function also as a custom builder function.
6
7Before reading this topic, you are advised to read [Basic Syntax Overview](./arkts-basic-syntax-overview.md), [Declarative UI Description](./arkts-declarative-ui-description.md), and [Creating a Custom Component](./arkts-create-custom-components.md).
8
9> **NOTE**
10>
11> This decorator can be used in ArkTS widgets since API version 9.
12>
13> This decorator can be used in atomic services since API version 11.
14
15
16## Rules of Use
17
18### Private Custom Builder Function
19
20Syntax:
21
22```ts
23@Builder MyBuilderFunction() {}
24```
25
26Usage:
27
28```ts
29this.MyBuilderFunction()
30```
31
32- You can define one or more @Builder decorated methods in a custom component. Such a method is considered as a private, special type of member function of the component.
33
34- Private custom builder functions can be called in custom components, **build()**, and other custom builder functions.
35
36- Inside the custom builder function body, **this** refers to the owning component. Component state variables are accessible from within the custom builder function implementation. Using **this** to access the custom components' state variables is recommended over parameter passing.
37
38
39### Global Custom Builder Function
40
41Syntax:
42
43```ts
44@Builder function MyGlobalBuilderFunction() { ... }
45```
46
47Usage:
48
49```ts
50MyGlobalBuilderFunction()
51```
52
53- Use of a global custom builder function is recommended if no own state is involved.
54
55- Global custom builder functions can be called in **build()** and other custom builder functions.
56
57
58## Parameter Passing Rules
59
60For custom builder functions, parameters can be passed [by value](#by-value-parameter-passing) and [by reference](#by-reference-parameter-passing). Both of them must comply with the following rules:
61
62- The parameter type must be the same as the declared parameter type. The **undefined** or **null** constants as well as expressions evaluating to these values are not allowed.
63
64- All parameters must be immutable inside the custom builder function.
65
66- The custom builder function body follows the same [syntax rules](arkts-create-custom-components.md#build-function) as **build()**.
67
68- Parameters are passed by value in all cases except when only one parameter is passed in and the parameter needs to be directly passed to the object literal.
69
70
71### By-Reference Parameter Passing
72
73In by-reference parameter passing, state variables can be passed, and the change of these state variables causes the UI re-rendering in the \@Builder decorated method.
74
75```ts
76class Tmp {
77  paramA1: string = ''
78}
79
80@Builder function overBuilder(params: Tmp) {
81  Row() {
82    Text(`UseStateVarByReference: ${params.paramA1} `)
83  }
84}
85@Entry
86@Component
87struct Parent {
88  @State label: string = 'Hello';
89  build() {
90    Column() {
91      // When the overBuilder component is called in the parent component,
92      // pass this.label to the overBuilder component by reference.
93      overBuilder({ paramA1: this.label })
94      Button('Click me').onClick(() => {
95        // After you click "Click me", the UI text changes from "Hello" to "ArkUI".
96        this.label = 'ArkUI';
97      })
98    }
99  }
100}
101```
102
103When parameters are passed by reference, if a custom component is called within the \@Builder method, ArkUI provides [$$](arkts-two-way-sync.md) as the paradigm for passing parameters by reference.
104
105```ts
106class Tmp {
107  paramA1: string = ''
108}
109
110@Builder function overBuilder($$: Tmp) {
111  Row() {
112    Column() {
113      Text(`overBuilder===${$$.paramA1}`)
114      HelloComponent({message: $$.paramA1})
115    }
116  }
117}
118
119@Component
120struct HelloComponent {
121  @Prop message: string;
122
123  build() {
124    Row() {
125      Text(`HelloComponent===${this.message}`)
126    }
127  }
128}
129
130@Entry
131@Component
132struct Parent {
133  @State label: string = 'Hello';
134  build() {
135    Column() {
136      // When the overBuilder component is called in the parent component,
137      // pass this.label to the overBuilder component by reference.
138      overBuilder({paramA1: this.label})
139      Button('Click me').onClick(() => {
140        // After you click "Click me", the UI text changes from "Hello" to "ArkUI".
141        this.label = 'ArkUI';
142      })
143    }
144  }
145}
146```
147
148### By-Value Parameter Passing
149
150By default, parameters in the \@Builder decorated functions are passed by value. In this case, when the passed parameter is a state variable, the change of the state variable does not cause UI re-rendering in the \@Builder decorated function. Therefore, when passing state variables, you are advised to use [by-reference parameter passing](#by-reference-parameter-passing).
151
152```ts
153@Builder function overBuilder(paramA1: string) {
154  Row() {
155    Text(`UseStateVarByValue: ${paramA1} `)
156  }
157}
158@Entry
159@Component
160struct Parent {
161  @State label: string = 'Hello';
162  build() {
163    Column() {
164      overBuilder(this.label)
165    }
166  }
167}
168```
169
170In the way of passing parameters by value, the @ObservedV2 and @Trace decorators can be used together in the @ComponentV2 decorated custom component to re-render the UI.
171
172[Positive Example]
173
174In @ComponentV2, only the @ObservedV2 decorated **ParamTmp** class and the @Trace decorated **count** property can trigger the UI re-render.
175
176```ts
177@ObservedV2
178class ParamTmp {
179  @Trace count : number = 0;
180}
181
182@Builder
183function renderText(param: ParamTmp) {
184  Column() {
185    Text(`param : ${param.count}`)
186      .fontSize(20)
187      .fontWeight(FontWeight.Bold)
188  }
189}
190
191@Builder
192function renderMap(paramMap: Map<string,number>) {
193  Text(`paramMap : ${paramMap.get('name')}`)
194    .fontSize(20)
195    .fontWeight(FontWeight.Bold)
196}
197
198@Builder
199function renderSet(paramSet: Set<number>) {
200  Text(`paramSet : ${paramSet.size}`)
201    .fontSize(20)
202    .fontWeight(FontWeight.Bold)
203}
204
205@Builder
206function renderNumberArr(paramNumArr: number[]) {
207  Text(`paramNumArr : ${paramNumArr[0]}`)
208    .fontSize(20)
209    .fontWeight(FontWeight.Bold)
210}
211
212@Entry
213@ComponentV2
214struct PageBuilder {
215  @Local builderParams: ParamTmp = new ParamTmp();
216  @Local map_value: Map<string,number> = new Map();
217  @Local set_value: Set<number> = new Set([0]);
218  @Local numArr_value: number[] = [0];
219  private progressTimer: number = -1;
220
221  aboutToAppear(): void {
222    this.progressTimer = setInterval(() => {
223      if (this.builderParams.count < 100) {
224        this.builderParams.count += 5;
225        this.map_value.set('name', this.builderParams.count);
226        this.set_value.add(this.builderParams.count);
227        this.numArr_value[0] = this.builderParams.count;
228      } else {
229        clearInterval(this.progressTimer)
230      }
231    }, 500);
232  }
233
234  @Builder
235  localBuilder() {
236    Column() {
237      Text(`localBuilder : ${this.builderParams.count}`)
238        .fontSize(20)
239        .fontWeight(FontWeight.Bold)
240    }
241  }
242
243  build() {
244    Column() {
245      this.localBuilder()
246      Text(`builderParams :${this.builderParams.count}`)
247        .fontSize(20)
248        .fontWeight(FontWeight.Bold)
249      renderText(this.builderParams)
250      renderText({ count: this.builderParams.count })
251      renderMap(this.map_value)
252      renderSet(this.set_value)
253      renderNumberArr(this.numArr_value)
254    }
255    .width('100%')
256    .height('100%')
257  }
258}
259```
260
261[Negative Example]
262
263In the @ComponentV2 decorated custom component, the use of simple data types cannot trigger UI re-render.
264
265```ts
266@ObservedV2
267class ParamTmp {
268  @Trace count : number = 0;
269}
270
271@Builder
272function renderNumber(paramNum: number) {
273  Text(`paramNum : ${paramNum}`)
274    .fontSize(30)
275    .fontWeight(FontWeight.Bold)
276}
277
278@Entry
279@ComponentV2
280struct PageBuilder {
281  @Local class_value: ParamTmp = new ParamTmp();
282  // Using simple data type cannot trigger UI re-render
283  @Local num_value: number = 0;
284  private progressTimer: number = -1;
285
286  aboutToAppear(): void {
287    this.progressTimer = setInterval(() => {
288      if (this.class_value.count < 100) {
289        this.class_value.count += 5;
290        this.num_value += 5;
291      } else {
292        clearInterval(this.progressTimer)
293      }
294    }, 500);
295  }
296
297  build() {
298    Column() {
299      renderNumber(this.num_value)
300    }
301    .width('100%')
302    .height('100%')
303    .padding(50)
304  }
305}
306```
307
308## Constraints
309
3101. Parameter values cannot be changed in \@Builder decorated functions. Otherwise, the framework throws a runtime error. You can change the parameters in the \@Builder decorated custom components.
311
312```ts
313interface Temp {
314  paramA: string;
315}
316
317@Builder function overBuilder($$: Temp) {
318  Row() {
319    Column() {
320      Button(`overBuilder === ${$$.paramA}`)
321        .onClick(() => {
322          // Incorrect format. Parameter values cannot be changed in the function decorated by @Builder.
323          $$.paramA = 'Yes';
324      })
325    }
326  }
327}
328
329@Entry
330@Component
331struct Parent {
332  @State label: string = 'Hello';
333
334  build() {
335    Column() {
336      overBuilder({paramA: this.label})
337      Button('click me')
338        .onClick(() => {
339          this.label = 'ArkUI';
340        })
341    }
342  }
343}
344```
345
3462. The \@Builder triggers dynamic UI rendering for only when parameters are passed in by reference. Only one parameter can be passed.
347
3483. If the \@Builder passes in two or more parameters, dynamic UI rendering is not triggered.
349
3504. If the \@Builder passes in parameters by value and by reference, dynamic UI rendering is not triggered.
351
3525. \@Builder parameters must be passed in one by one in the form of object literals to trigger dynamic UI rendering.
353
354
355## Use Scenarios
356
357### Using Custom Builder Function in Custom Component
358
359Create a private \@Builder method, call this method by using **this.builder()** in **Column**, and change the content of **builder_value** through the **aboutToAppear** lifecycle function and Button click event to dynamically render the UI.
360
361```ts
362@Entry
363@Component
364struct PrivateBuilder {
365  @State builder_value: string = 'Hello';
366
367  @Builder builder() {
368    Column(){
369      Text(this.builder_value)
370        .fontSize(30)
371        .fontWeight(FontWeight.Bold)
372    }
373  }
374
375  aboutToAppear(): void {
376    setTimeout(() => {
377      this.builder_value = 'Hello World';
378    },3000)
379  }
380
381  build() {
382    Row() {
383      Column() {
384        Text(this.builder_value)
385          .fontSize(30)
386          .fontWeight(FontWeight.Bold)
387        this.builder()
388        Button('Click to change builder_value')
389          .onClick(() => {
390            this.builder_value = 'builder_value clicked'
391          })
392      }
393    }
394  }
395}
396```
397
398### Using Global Custom Builder Function
399
400Create a global \@Builder method and call this method by using **overBuilder()** in **Column**. Pass the simple type or complex type parameters in the form of object literals, value changes will trigger UI re-rendering.
401
402```ts
403class ChildTmp {
404  val: number = 1;
405}
406
407class Tmp {
408  str_value: string = 'Hello';
409  num_value: number = 0;
410  tmp_value: ChildTmp = new ChildTmp();
411  arrayTmp_value: Array<ChildTmp> = [];
412}
413
414@Builder function overBuilder(param: Tmp) {
415  Column() {
416    Text(`str_value: ${param.str_value}`)
417    Text(`num_value: ${param.num_value}`)
418    Text(`tmp_value: ${param.tmp_value.val}`)
419    ForEach(param.arrayTmp_value, (item: ChildTmp) => {
420      Text(`arrayTmp_value: ${item.val}`)
421    }, (item: ChildTmp) => JSON.stringify(item))
422  }
423}
424
425@Entry
426@Component
427struct Parent {
428  @State objParam: Tmp = new Tmp();
429  build() {
430    Column() {
431      Text('Render the UI by calling the @Builder')
432        .fontSize(20)
433      overBuilder({str_value: this.objParam.str_value, num_value: this.objParam.num_value,
434       tmp_value: this.objParam.tmp_value, arrayTmp_value: this.objParam.arrayTmp_value})
435      Line()
436        .width('100%')
437        .height(10)
438        .backgroundColor('#000000').margin(10)
439      Button('Click to change parameter').onClick(() => {
440        this.objParam.str_value = 'Hello World';
441        this.objParam.num_value = 1;
442        this.objParam.tmp_value.val = 8;
443        const child_value: ChildTmp = {
444          val: 2
445        }
446        this.objParam.arrayTmp_value.push(child_value)
447      })
448    }
449  }
450}
451```
452
453### Changing the Variables Decorated by the Decorator Triggers UI Re-rendering
454
455In this case, the decorator feature is used. The change of the listening value triggers UI re-rendering and parameters are not passed through the \@Builder.
456
457```ts
458class Tmp {
459  str_value: string = 'Hello';
460}
461
462@Entry
463@Component
464struct Parent {
465  @State objParam: Tmp = new Tmp();
466  @State label: string = 'World';
467
468  @Builder privateBuilder() {
469    Column() {
470      Text(`wrapBuilder str_value: ${this.objParam.str_value}`)
471      Text(`wrapBuilder num: ${this.label}`)
472    }
473  }
474
475  build() {
476    Column() {
477      Text('Render the UI by calling the @Builder')
478        .fontSize(20)
479      this.privateBuilder()
480      Line()
481        .width('100%')
482        .height(10)
483        .backgroundColor('#000000').margin(10)
484      Button('Click to change parameter').onClick(() => {
485        this.objParam.str_value = 'str_value Hello World';
486        this.label = 'label Hello World'
487      })
488    }
489  }
490}
491```
492
493### Using the Global and Local @Builder to Pass in Parameters of the customBuilder Type
494
495```ts
496@Builder
497function overBuilder() {
498  Row() {
499    Text('Global Builder')
500      .fontSize(30)
501      .fontWeight(FontWeight.Bold)
502  }
503}
504
505@Entry
506@Component
507struct customBuilderDemo {
508  @State arr: number[] = [0, 1, 2, 3, 4];
509
510  @Builder privateBuilder() {
511    Row() {
512      Text('Local Builder')
513        .fontSize(30)
514        .fontWeight(FontWeight.Bold)
515    }
516  }
517
518  build() {
519    Column() {
520      List({ space: 10 }) {
521        ForEach(this.arr, (item: number) => {
522          ListItem(){
523            Text(`${item}`)
524              .width('100%')
525              .height(100)
526              .fontSize(16)
527              .textAlign(TextAlign.Center)
528              .borderRadius(10)
529              .backgroundColor(0xFFFFFF)
530          }
531            .swipeAction({
532              start: {
533                builder: overBuilder()
534              },
535              end: {
536                builder: () => { this.privateBuilder() }
537              }
538            })
539        }, (item: string) => JSON.stringify(item))
540      }
541    }
542  }
543}
544```
545
546### Nesting of Multi-layer \@Builder Method
547
548Call the custom components or other methods within \@Builder method. ArkUI provides [$$](arkts-two-way-sync.md) as a paradigm for passing parameters by reference.
549
550```ts
551class Tmp {
552  paramA1: string = '';
553}
554
555@Builder function parentBuilder($$: Tmp) {
556  Row() {
557    Column() {
558      Text(`parentBuilder===${$$.paramA1}`)
559        .fontSize(30)
560        .fontWeight(FontWeight.Bold)
561      HelloComponent({message: $$.paramA1})
562      childBuilder({paramA1: $$.paramA1})
563    }
564  }
565}
566
567@Component
568struct HelloComponent {
569  @Prop message: string = '';
570
571  build() {
572    Row() {
573      Text(`HelloComponent===${this.message}`)
574        .fontSize(30)
575        .fontWeight(FontWeight.Bold)
576    }
577  }
578}
579
580@Builder
581function childBuilder($$: Tmp) {
582  Row() {
583    Column() {
584      Text(`childBuilder===${$$.paramA1}`)
585        .fontSize(30)
586        .fontWeight(FontWeight.Bold)
587      HelloChildComponent({message: $$.paramA1})
588      grandsonBuilder({paramA1: $$.paramA1})
589    }
590  }
591}
592
593@Component
594struct HelloChildComponent {
595  @Prop message: string = '';
596  build() {
597    Row() {
598      Text(`HelloChildComponent===${this.message}`)
599        .fontSize(30)
600        .fontWeight(FontWeight.Bold)
601    }
602  }
603}
604
605@Builder function grandsonBuilder($$: Tmp) {
606  Row() {
607    Column() {
608      Text(`grandsonBuilder===${$$.paramA1}`)
609        .fontSize(30)
610        .fontWeight(FontWeight.Bold)
611      HelloGrandsonComponent({message: $$.paramA1})
612    }
613  }
614}
615
616@Component
617struct HelloGrandsonComponent {
618  @Prop message: string;
619  build() {
620    Row() {
621      Text(`HelloGrandsonComponent===${this.message}`)
622        .fontSize(30)
623        .fontWeight(FontWeight.Bold)
624    }
625  }
626}
627
628@Entry
629@Component
630struct Parent {
631  @State label: string = 'Hello';
632  build() {
633    Column() {
634      parentBuilder({paramA1: this.label})
635      Button('Click me').onClick(() => {
636        this.label = 'ArkUI';
637      })
638    }
639  }
640}
641```
642
643### Using \@Builder Functions Together with the Decorators in V2
644
645Call the global @Builder and local @Builder in the @ComponentV2 decorated custom component to change related variables, triggering UI re-renders.
646
647```ts
648@ObservedV2
649class Info {
650  @Trace name: string = '';
651  @Trace age: number = 0;
652}
653
654@Builder
655function overBuilder(param: Info) {
656  Column() {
657    Text('Global @Builder name :${param.name}`)
658      .fontSize(30)
659      .fontWeight(FontWeight.Bold)
660    Text('Global @Builder age :${param.age}`)
661      .fontSize(30)
662      .fontWeight(FontWeight.Bold)
663  }
664}
665
666@ComponentV2
667struct ChildPage {
668  @Require @Param childInfo: Info;
669  build() {
670    overBuilder({name: this.childInfo.name, age: this.childInfo.age})
671  }
672}
673
674@Entry
675@ComponentV2
676struct ParentPage {
677  info1: Info = { name: "Tom", age: 25 };
678  @Local info2: Info = { name: "Tom", age: 25 };
679
680  @Builder
681  privateBuilder() {
682    Column() {
683      Text('Local @Builder name :${this.info1.name}`)
684        .fontSize(30)
685        .fontWeight(FontWeight.Bold)
686      Text('Local @Builder age :${this.info1.age}`)
687        .fontSize(30)
688        .fontWeight(FontWeight.Bold)
689    }
690  }
691
692  build() {
693    Column() {
694      Text(`info1: ${this.info1.name}  ${this.info1.age}`) // Text1
695        .fontSize(30)
696        .fontWeight(FontWeight.Bold)
697      this.privateBuilder() // Call the local @Builder.
698      Line()
699        .width('100%')
700        .height(10)
701        .backgroundColor('#000000').margin(10)
702      Text(`info2: ${this.info2.name}  ${this.info2.age}`) // Text2
703        .fontSize(30)
704        .fontWeight(FontWeight.Bold)
705      overBuilder({ name: this.info2.name, age: this.info2.age}) // Call the global @Builder.
706      Line()
707        .width('100%')
708        .height(10)
709        .backgroundColor('#000000').margin(10)
710      Text(`info1: ${this.info1.name}  ${this.info1.age}`) // Text1
711        .fontSize(30)
712        .fontWeight(FontWeight.Bold)
713      ChildPage ({childInfo: this.info1}) // Call the custom component.
714      Line()
715        .width('100%')
716        .height(10)
717        .backgroundColor('#000000').margin(10)
718      Text(`info2: ${this.info2.name}  ${this.info2.age}`) // Text2
719        .fontSize(30)
720        .fontWeight(FontWeight.Bold)
721      ChildPage ({childInfo: this.info2}) // Call the custom component.
722      Line()
723        .width('100%')
724        .height(10)
725        .backgroundColor('#000000').margin(10)
726      Button("change info1&info2")
727        .onClick(() => {
728          this.info1 = { name: "Cat", age: 18} // Text1 is not re-rendered because no decorator is used to listen for value changes.
729          this.info2 = { name: "Cat", age: 18} // Text2 is re-rendered because a decorator is used to listen for value changes.
730        })
731    }
732  }
733}
734```
735
736## FAQs
737
738### Two or More Parameters Are Used in the \@Builder
739
740When two or more parameters are used, the value change does not trigger the UI re-rendering even if the parameters are passed in the form of object literals.
741
742[Negative Example]
743
744```ts
745class GlobalTmp {
746  str_value: string = 'Hello';
747}
748
749@Builder function overBuilder(param: GlobalTmp, num: number) {
750  Column() {
751    Text(`str_value: ${param.str_value}`)
752    Text(`num: ${num}`)
753  }
754}
755
756@Entry
757@Component
758struct Parent {
759  @State objParam: GlobalTmp = new GlobalTmp();
760  @State num: number = 0;
761  build() {
762    Column() {
763      Text('Render the UI by calling the @Builder')
764        .fontSize(20)
765      // Two parameters are used, which is incorrect.
766      overBuilder({str_value: this.objParam.str_value}, this.num)
767      Line()
768        .width('100%')
769        .height(10)
770        .backgroundColor('#000000').margin(10)
771      Button('Click to change parameter').onClick(() => {
772        this.objParam.str_value = 'Hello World';
773        this.num = 1;
774      })
775    }
776  }
777}
778```
779
780[Negative Example]
781
782```ts
783class GlobalTmp {
784  str_value: string = 'Hello';
785}
786class SecondTmp {
787  num_value: number = 0;
788}
789@Builder function overBuilder(param: GlobalTmp, num: SecondTmp) {
790  Column() {
791    Text(`str_value: ${param.str_value}`)
792    Text(`num: ${num.num_value}`)
793  }
794}
795
796@Entry
797@Component
798struct Parent {
799  @State strParam: GlobalTmp = new GlobalTmp();
800  @State numParam: SecondTmp = new SecondTmp();
801  build() {
802    Column() {
803      Text('Render the UI by calling the @Builder')
804        .fontSize(20)
805      // Two parameters are used, which is incorrect.
806      overBuilder({str_value: this.strParam.str_value}, {num_value: this.numParam.num_value})
807      Line()
808        .width('100%')
809        .height(10)
810        .backgroundColor('#000000').margin(10)
811      Button('Click to change parameter').onClick(() => {
812        this.strParam.str_value = 'Hello World';
813        this.numParam.num_value = 1;
814      })
815    }
816  }
817}
818```
819
820Only one parameter can be used in the \@Builder. When one parameter is passed in the form of object literals, the value change triggers the UI re-rendering.
821
822[Positive Example]
823
824```ts
825class GlobalTmp {
826  str_value: string = 'Hello';
827  num_value: number = 0;
828}
829@Builder function overBuilder(param: GlobalTmp) {
830  Column() {
831    Text(`str_value: ${param.str_value}`)
832    Text(`num: ${param.num_value}`)
833  }
834}
835
836@Entry
837@Component
838struct Parent {
839  @State objParam: GlobalTmp = new GlobalTmp();
840  build() {
841    Column() {
842      Text('Render the UI by calling the @Builder')
843        .fontSize(20)
844      overBuilder({str_value: this.objParam.str_value, num_value: this.objParam.num_value})
845      Line()
846        .width('100%')
847        .height(10)
848        .backgroundColor('#000000').margin(10)
849      Button('Click to change parameter').onClick(() => {
850        this.objParam.str_value = 'Hello World';
851        this.objParam.num_value = 1;
852      })
853    }
854  }
855}
856```
857