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