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