1# \@Builder Decorator: Custom Builder Function 2 3ArkUI provides the \@Builder decorator that is a lightweight UI element reuse mechanism. This custom component 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 7 8> **NOTE** 9> 10> This decorator can be used in ArkTS widgets since API version 9. 11> 12> This decorator can be used in atomic services since API version 11. 13 14## Constraints 15 16- The \@Builder triggers dynamic UI rendering for only when parameters are passed in by reference. Only one parameter can be passed. 17 18- If the \@Builder passes in two or more parameters, dynamic UI rendering is not triggered. 19 20- If the \@Builder passes in parameters by value and by reference, dynamic UI rendering is not triggered. 21 22- \@Builder parameters must be passed in one by one in the form of object literals to trigger dynamic UI rendering. 23 24## Rules of Use 25 26### Private Custom Builder Function 27 28Syntax: 29 30```ts 31@Builder MyBuilderFunction() {} 32``` 33 34Usage: 35 36```ts 37this.MyBuilderFunction() 38``` 39 40- 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. 41 42- The custom builder function can be called from the **build** method or another custom builder function in the same component only. 43 44- 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. 45 46 47### Global Custom Builder Function 48 49Syntax: 50 51```ts 52@Builder function MyGlobalBuilderFunction() { ... } 53``` 54 55Usage: 56 57```ts 58MyGlobalBuilderFunction() 59``` 60 61- Use of a global custom builder function is recommended if no own state is involved. 62 63 64## Parameter Passing Rules 65 66For 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: 67 68- 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. 69 70- All parameters must be immutable inside the custom builder function. 71 72- The custom builder function body follows the same [syntax rules](arkts-create-custom-components.md#build-function) as the **build()** function. 73 74- 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. 75 76 77### By-Reference Parameter Passing 78 79In 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. 80 81```ts 82class Tmp { 83 paramA1: string = '' 84} 85 86@Builder function overBuilder(params: Tmp) { 87 Row() { 88 Text(`UseStateVarByReference: ${params.paramA1} `) 89 } 90} 91@Entry 92@Component 93struct Parent { 94 @State label: string = 'Hello'; 95 build() { 96 Column() { 97 // Pass the this.label reference to the overBuilder component when the overBuilder component is called in the Parent component. 98 overBuilder({ paramA1: this.label }) 99 Button('Click me').onClick(() => { 100 // After Click me is clicked, the UI text changes from Hello to ArkUI. 101 this.label = 'ArkUI'; 102 }) 103 } 104 } 105} 106``` 107 108When 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. 109 110```ts 111class Tmp { 112 paramA1: string = '' 113} 114 115@Builder function overBuilder($$: Tmp) { 116 Row() { 117 Column() { 118 Text(`overBuilder===${$$.paramA1}`) 119 HelloComponent({message: $$.paramA1}) 120 } 121 } 122} 123 124@Component 125struct HelloComponent { 126 @Prop message: string; 127 128 build() { 129 Row() { 130 Text(`HelloComponent===${this.message}`) 131 } 132 } 133} 134 135@Entry 136@Component 137struct Parent { 138 @State label: string = 'Hello'; 139 build() { 140 Column() { 141 // Pass the this.label reference to the overBuilder component when the overBuilder component is called in the Parent component. 142 overBuilder({paramA1: this.label}) 143 Button('Click me').onClick(() => { 144 // After Click me is clicked, the UI text changes from Hello to ArkUI. 145 this.label = 'ArkUI'; 146 }) 147 } 148 } 149} 150``` 151 152### By-Value Parameter Passing 153 154By 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). 155 156 157```ts 158@Builder function overBuilder(paramA1: string) { 159 Row() { 160 Text(`UseStateVarByValue: ${paramA1} `) 161 } 162} 163@Entry 164@Component 165struct Parent { 166 @State label: string = 'Hello'; 167 build() { 168 Column() { 169 overBuilder(this.label) 170 } 171 } 172} 173``` 174 175## Use Scenarios 176 177### Using Custom Builder Function in Custom Component 178 179Create 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. 180 181```ts 182@Entry 183@Component 184struct PrivateBuilder { 185 @State builder_value: string = 'Hello'; 186 187 @Builder builder() { 188 Column(){ 189 Text(this.builder_value) 190 .fontSize(30) 191 .fontWeight(FontWeight.Bold) 192 } 193 } 194 195 aboutToAppear(): void { 196 setTimeout(() => { 197 this.builder_value = 'Hello World'; 198 },3000) 199 } 200 201 build() { 202 Row() { 203 Column() { 204 Text(this.builder_value) 205 .fontSize(30) 206 .fontWeight(FontWeight.Bold) 207 this.builder() 208 Button('Click to change builder_value') 209 .onClick(() => { 210 this.builder_value = 'builder_value clicked' 211 }) 212 } 213 } 214 } 215} 216``` 217 218### Using Global Custom Builder Function 219 220Create 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. 221 222```ts 223class ChildTmp { 224 val: number = 1; 225} 226 227class Tmp { 228 str_value: string = 'Hello'; 229 num_value: number = 0; 230 tmp_value: ChildTmp = new ChildTmp(); 231 arrayTmp_value: Array<ChildTmp> = []; 232} 233 234@Builder function overBuilder(param: Tmp) { 235 Column() { 236 Text(`str_value: ${param.str_value}`) 237 Text(`num_value: ${param.num_value}`) 238 Text(`tmp_value: ${param.tmp_value.val}`) 239 ForEach(param.arrayTmp_value, (item: ChildTmp) => { 240 Text(`arrayTmp_value: ${item.val}`) 241 }, (item: ChildTmp) => JSON.stringify(item)) 242 } 243} 244 245@Entry 246@Component 247struct Parent { 248 @State objParam: Tmp = new Tmp(); 249 build() { 250 Column() { 251 Text('Render the UI by calling the @Builder') 252 .fontSize(20) 253 overBuilder({str_value: this.objParam.str_value, num_value: this.objParam.num_value, tmp_value: this.objParam.tmp_value, arrayTmp_value: this.objParam.arrayTmp_value}) 254 Line() 255 .width('100%') 256 .height(10) 257 .backgroundColor('#000000').margin(10) 258 Button('Click to change parameter').onClick(() => { 259 this.objParam.str_value = 'Hello World'; 260 this.objParam.num_value = 1; 261 this.objParam.tmp_value.val = 8; 262 const child_value: ChildTmp = { 263 val: 2 264 } 265 this.objParam.arrayTmp_value.push(child_value) 266 }) 267 } 268 } 269} 270``` 271 272### Changing the Variables Decorated by the Decorator Triggers UI Re-rendering 273 274In 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. 275 276```ts 277class Tmp { 278 str_value: string = 'Hello'; 279} 280 281@Entry 282@Component 283struct Parent { 284 @State objParam: Tmp = new Tmp(); 285 @State label: string = 'World'; 286 287 @Builder privateBuilder() { 288 Column() { 289 Text(`wrapBuilder str_value: ${this.objParam.str_value}`) 290 Text(`wrapBuilder num: ${this.label}`) 291 } 292 } 293 294 build() { 295 Column() { 296 Text('Render the UI by calling the @Builder') 297 .fontSize(20) 298 this.privateBuilder() 299 Line() 300 .width('100%') 301 .height(10) 302 .backgroundColor('#000000').margin(10) 303 Button('Click to change parameter').onClick(() => { 304 this.objParam.str_value = 'str_value Hello World'; 305 this.label = 'label Hello World' 306 }) 307 } 308 } 309} 310``` 311 312### Using the Global and Local @Builder to Pass in Parameters of the customBuilder Type 313 314```ts 315@Builder 316function overBuilder() { 317 Row() { 318 Text('Global Builder') 319 .fontSize(30) 320 .fontWeight(FontWeight.Bold) 321 } 322} 323 324@Entry 325@Component 326struct customBuilderDemo { 327 @State arr: number[] = [0, 1, 2, 3, 4]; 328 329 @Builder privateBuilder() { 330 Row() { 331 Text('Local Builder') 332 .fontSize(30) 333 .fontWeight(FontWeight.Bold) 334 } 335 } 336 337 build() { 338 Column() { 339 List({ space: 10 }) { 340 ForEach(this.arr, (item: number) => { 341 ListItem(){ 342 Text(`${item}`) 343 .width('100%') 344 .height(100) 345 .fontSize(16) 346 .textAlign(TextAlign.Center) 347 .borderRadius(10) 348 .backgroundColor(0xFFFFFF) 349 } 350 .swipeAction({ 351 start: { 352 builder: overBuilder() 353 }, 354 end: { 355 builder: () => { this.privateBuilder() } 356 } 357 }) 358 }, (item: string) => JSON.stringify(item)) 359 } 360 } 361 } 362} 363``` 364 365### Nesting of Multi-layer \@Builder Method 366 367Call the custom components or other methods within \@Builder method. ArkUI provides [$$](arkts-two-way-sync.md) as a paradigm for passing parameters by reference. 368 369```ts 370class Tmp { 371 paramA1: string = ''; 372} 373 374@Builder function parentBuilder($$: Tmp) { 375 Row() { 376 Column() { 377 Text(`parentBuilder===${$$.paramA1}`) 378 .fontSize(30) 379 .fontWeight(FontWeight.Bold) 380 HelloComponent({message: $$.paramA1}) 381 childBuilder({paramA1: $$.paramA1}) 382 } 383 } 384} 385 386@Component 387struct HelloComponent { 388 @Prop message: string = ''; 389 390 build() { 391 Row() { 392 Text(`HelloComponent===${this.message}`) 393 .fontSize(30) 394 .fontWeight(FontWeight.Bold) 395 } 396 } 397} 398 399@Builder 400function childBuilder($$: Tmp) { 401 Row() { 402 Column() { 403 Text(`childBuilder===${$$.paramA1}`) 404 .fontSize(30) 405 .fontWeight(FontWeight.Bold) 406 HelloChildComponent({message: $$.paramA1}) 407 grandsonBuilder({paramA1: $$.paramA1}) 408 } 409 } 410} 411 412@Component 413struct HelloChildComponent { 414 @State message: string = ''; 415 build() { 416 Row() { 417 Text(`HelloChildComponent===${this.message}`) 418 .fontSize(30) 419 .fontWeight(FontWeight.Bold) 420 } 421 } 422} 423 424@Builder function grandsonBuilder($$: Tmp) { 425 Row() { 426 Column() { 427 Text(`grandsonBuilder===${$$.paramA1}`) 428 .fontSize(30) 429 .fontWeight(FontWeight.Bold) 430 HelloGrandsonComponent({message: $$.paramA1}) 431 } 432 } 433} 434 435@Component 436struct HelloGrandsonComponent { 437 @Prop message: string; 438 build() { 439 Row() { 440 Text(`HelloGrandsonComponent===${this.message}`) 441 .fontSize(30) 442 .fontWeight(FontWeight.Bold) 443 } 444 } 445} 446 447@Entry 448@Component 449struct Parent { 450 @State label: string = 'Hello'; 451 build() { 452 Column() { 453 parentBuilder({paramA1: this.label}) 454 Button('Click me').onClick(() => { 455 this.label = 'ArkUI'; 456 }) 457 } 458 } 459} 460``` 461 462## FAQs 463 464### Two or More Parameters Are Used in the \@Builder 465 466When 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. 467 468[Incorrect Example] 469 470```ts 471class GlobalTmp { 472 str_value: string = 'Hello'; 473} 474 475@Builder function overBuilder(param: GlobalTmp, num: number) { 476 Column() { 477 Text(`str_value: ${param.str_value}`) 478 Text(`num: ${num}`) 479 } 480} 481 482@Entry 483@Component 484struct Parent { 485 @State objParam: GlobalTmp = new GlobalTmp(); 486 @State num: number = 0; 487 build() { 488 Column() { 489 Text('Render the UI by calling the @Builder') 490 .fontSize(20) 491 overBuilder({str_value: this.objParam.str_value}, this.num) // Two parameters are used. 492 Line() 493 .width('100%') 494 .height(10) 495 .backgroundColor('#000000').margin(10) 496 Button('Click to change parameter').onClick(() => { 497 this.objParam.str_value = 'Hello World'; 498 this.num = 1; 499 }) 500 } 501 } 502} 503``` 504 505[Incorrect Example] 506 507```ts 508class GlobalTmp { 509 str_value: string = 'Hello'; 510} 511class SecondTmp { 512 num_value: number = 0; 513} 514@Builder function overBuilder(param: GlobalTmp, num: SecondTmp) { 515 Column() { 516 Text(`str_value: ${param.str_value}`) 517 Text(`num: ${num.num_value}`) 518 } 519} 520 521@Entry 522@Component 523struct Parent { 524 @State strParam: GlobalTmp = new GlobalTmp(); 525 @State numParam: SecondTmp = new SecondTmp(); 526 build() { 527 Column() { 528 Text('Render the UI by calling the @Builder') 529 .fontSize(20) 530 overBuilder({str_value: this.strParam.str_value}, {num_value: this.numParam.num_value}) // Two parameters are used. 531 Line() 532 .width('100%') 533 .height(10) 534 .backgroundColor('#000000').margin(10) 535 Button('Click to change parameter').onClick(() => { 536 this.strParam.str_value = 'Hello World'; 537 this.numParam.num_value = 1; 538 }) 539 } 540 } 541} 542``` 543 544Only 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. 545 546[Correct Example] 547 548```ts 549class GlobalTmp { 550 str_value: string = 'Hello'; 551 num_value: number = 0; 552} 553@Builder function overBuilder(param: GlobalTmp) { 554 Column() { 555 Text(`str_value: ${param.str_value}`) 556 Text(`num: ${param.num_value}`) 557 } 558} 559 560@Entry 561@Component 562struct Parent { 563 @State objParam: GlobalTmp = new GlobalTmp(); 564 build() { 565 Column() { 566 Text('Render the UI by calling the @Builder') 567 .fontSize(20) 568 overBuilder({str_value: this.objParam.str_value, num_value: this.objParam.num_value}) 569 Line() 570 .width('100%') 571 .height(10) 572 .backgroundColor('#000000').margin(10) 573 Button('Click to change parameter').onClick(() => { 574 this.objParam.str_value = 'Hello World'; 575 this.objParam.num_value = 1; 576 }) 577 } 578 } 579} 580``` 581