1# \@LocalBuilder装饰器: 维持组件关系 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @zhangboren--> 5<!--Designer: @zhangboren--> 6<!--Tester: @TerryTsao--> 7<!--Adviser: @zhang_yixin13--> 8 9当开发者使用局部\@Builder进行引用数据传递时,需要考虑组件的父子关系。然而在使用.bind(this)的方式更改函数调用上下文后,会出现组件的父子关系与状态管理的父子关系不一致的问题。为了解决这一问题,引入\@LocalBuilder装饰器。\@LocalBuilder拥有和局部\@Builder相同的功能,且比局部\@Builder能够更好的确定组件的父子关系和状态管理的父子关系。 10 11在阅读本文档前,建议提前阅读:[@Builder](./arkts-builder.md)。 12 13> **说明:** 14> 15> 从API version 12开始支持。 16> 17> 从API version 12开始,该装饰器支持在ArkTS卡片中使用。 18> 19> 从API version 12开始,该装饰器支持在原子化服务中使用。 20 21## 装饰器使用说明 22 23### 自定义组件内自定义构建函数 24 25定义的语法: 26 27```ts 28@LocalBuilder myBuilderFunction() { ... } 29``` 30 31使用方法: 32 33```ts 34this.myBuilderFunction() 35``` 36- 允许在自定义组件内定义一个或多个@LocalBuilder函数,该函数被认为是该组件的私有、特殊类型的成员函数。 37 38- 自定义构建函数可以在所属组件的build函数和其他自定义构建函数中调用,但不允许在组件外调用。 39 40- 在自定义函数体中,this指代当前所属组件,组件的状态变量可以在自定义构建函数内访问。建议通过this访问自定义组件的状态变量而不是参数传递。 41 42## 限制条件 43 44- \@LocalBuilder只能在所属组件内声明,不允许全局声明。 45 46- \@LocalBuilder不能与内置装饰器或自定义装饰器一起使用。 47 48- 在自定义组件中,\@LocalBuilder不能用来装饰静态函数。 49 50## \@LocalBuilder和局部\@Builder使用区别 51 52跨组件传递局部\@Builder函数时,会使用.bind(this)更改函数上下文,但这可能会导致组件的父子关系与状态管理的父子关系不一致。而\@LocalBuilder无论是否使用.bind(this),都不会改变组件的父子关系,即\@LocalBuilder中定义组件所属的父组件是确定的,无法被改变。详情请参考[@LocalBuilder和@Builder区别说明](arkts-localBuilder.md#localbuilder和builder区别说明)。 53 54 55 56> **说明:** 57> 58> bind()方法创建一个新的函数,称为绑定函数,当调用者绑定bind()时,该绑定函数会以创建时传入的第一个this作为原函数的this。 59 60## 参数传递规则 61 62\@LocalBuilder函数的参数传递有[按值传递](#按值传递参数)和[按引用传递](#按引用传递参数)两种,均需遵守以下规则: 63 64- 参数的类型必须与参数声明的类型一致,且不允许为undefined、null。 65 66- 在\@LocalBuilder修饰的函数内部,不允许改变参数值。 67 68- \@LocalBuilder内的UI语法遵循[UI语法规则](arkts-create-custom-components.md#build函数)。 69 70- 传入一个对象字面量参数时按引用传递,其他方式按值传递。 71 72### 按引用传递参数 73 74按引用传递参数时,传递的参数可为状态变量,且状态变量的改变会引起\@LocalBuilder函数内的UI刷新。 75 76> **说明:** 77> 78> 若\@LocalBuilder函数和$$参数一起使用,子组件调用父组件的@LocalBuilder函数,子组件传入的参数发生变化,不会引起\@LocalBuilder函数内的UI刷新。 79 80组件Parent内的\@LocalBuilder函数在build函数内调用,按键值对写法进行传值,当点击Click me时,\@LocalBuilder内的Text文本内容会随着状态变量内容的改变而改变。 81 82```ts 83class ReferenceType { 84 paramString: string = ''; 85} 86 87@Entry 88@Component 89struct Parent { 90 @State variableValue: string = 'Hello World'; 91 92 @LocalBuilder 93 citeLocalBuilder(params: ReferenceType) { 94 Row() { 95 Text(`UseStateVarByReference: ${params.paramString}`) 96 } 97 }; 98 99 build() { 100 Column() { 101 this.citeLocalBuilder({ paramString: this.variableValue }) 102 Button('Click me').onClick(() => { 103 this.variableValue = 'Hi World'; 104 }) 105 } 106 } 107} 108``` 109 110按引用传递参数时,如果在\@LocalBuilder函数内调用自定义组件,ArkUI提供[$$](arkts-two-way-sync.md)作为按引用传递参数的范式。 111 112组件Parent内的\@LocalBuilder函数内调用自定义组件,且按照引用传递参数将值传递到自定义组件,当Parent组件内状态变量值发生变化时,\@LocalBuilder函数内的自定义组件HelloComponent的message值也会发生变化。 113 114```ts 115class ReferenceType { 116 paramString: string = ''; 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 variableValue: string = 'Hello World'; 134 135 @LocalBuilder 136 citeLocalBuilder($$: ReferenceType) { 137 Row() { 138 Column() { 139 Text(`citeLocalBuilder===${$$.paramString}`) 140 HelloComponent({ message: $$.paramString }) 141 } 142 } 143 } 144 145 build() { 146 Column() { 147 this.citeLocalBuilder({ paramString: this.variableValue }) 148 Button('Click me').onClick(() => { 149 this.variableValue = 'Hi World'; 150 }) 151 } 152 } 153} 154``` 155 156当子组件引用父组件的\@LocalBuilder函数并传入状态变量时,状态变量的改变不会触发\@LocalBuilder函数内的UI刷新。这是因为调用\@LocalBuilder装饰的函数创建出来的组件绑定于父组件,而状态变量的刷新机制仅作用于当前组件及其子组件,对父组件无效。而使用\@Builder修饰函数可触发UI刷新,原因在于\@Builder改变了函数的this指向,使创建出来的组件绑定到子组件上,从而在子组件修改变量能够实现\@Builder中的UI刷新。 157 158组件Child将状态变量传递到Parent的\@Builder和\@LocalBuilder函数内。在\@Builder函数内,`this`指向Child,参数变化能触发UI刷新。在\@LocalBuilder函数内,`this`指向Parent,参数变化不会触发UI刷新。若\@LocalBuilder函数内引用Parent的状态变量发生变化,UI能正常刷新。 159 160```ts 161class Data { 162 size: number = 0; 163} 164 165@Entry 166@Component 167struct Parent { 168 label: string = 'parent'; 169 @State data: Data = new Data(); 170 171 @Builder 172 componentBuilder($$: Data) { 173 Text(`builder + $$`) 174 Text(`${'this -> ' + this.label}`) 175 Text(`${'size : ' + $$.size}`) 176 } 177 178 @LocalBuilder 179 componentLocalBuilder($$: Data) { 180 Text(`LocalBuilder + $$ data`) 181 Text(`${'this -> ' + this.label}`) 182 Text(`${'size : ' + $$.size}`) 183 } 184 185 @LocalBuilder 186 contentLocalBuilderNoArgument() { 187 Text(`LocalBuilder + local data`) 188 Text(`${'this -> ' + this.label}`) 189 Text(`${'size : ' + this.data.size}`) 190 } 191 192 build() { 193 Column() { 194 Child({ 195 contentBuilder: this.componentBuilder, 196 contentLocalBuilder: this.componentLocalBuilder, 197 contentLocalBuilderNoArgument: this.contentLocalBuilderNoArgument, 198 data: this.data 199 }) 200 } 201 } 202} 203 204@Component 205struct Child { 206 label: string = 'child'; 207 @Builder customBuilder() {}; 208 @BuilderParam contentBuilder: ((data: Data) => void) = this.customBuilder; 209 @BuilderParam contentLocalBuilder: ((data: Data) => void) = this.customBuilder; 210 @BuilderParam contentLocalBuilderNoArgument: (() => void) = this.customBuilder; 211 @Link data: Data; 212 213 build() { 214 Column() { 215 this.contentBuilder({ size: this.data.size }) 216 this.contentLocalBuilder({ size: this.data.size }) 217 this.contentLocalBuilderNoArgument() 218 Button('add child size').onClick(() => { 219 this.data.size += 1; 220 }) 221 } 222 } 223} 224``` 225 226### 按值传递参数 227 228调用\@LocalBuilder装饰的函数默认按值传递。当传递的参数为状态变量时,状态变量的改变不会引起\@LocalBuilder函数内的UI刷新。所以当使用状态变量的时候,推荐使用[按引用传递](#按引用传递参数)。 229 230组件Parent将\@State修饰的label值按照函数传参方式传递到\@LocalBuilder函数内,此时\@LocalBuilder函数获取到的值为普通变量值,所以改变\@State修饰的label值时,\@LocalBuilder函数内的值不会发生改变。 231 232```ts 233@Entry 234@Component 235struct Parent { 236 @State label: string = 'Hello'; 237 238 @LocalBuilder 239 citeLocalBuilder(paramA1: string) { 240 Row() { 241 Text(`UseStateVarByValue: ${paramA1}`) 242 } 243 } 244 245 build() { 246 Column() { 247 this.citeLocalBuilder(this.label) 248 } 249 } 250} 251``` 252 253## \@LocalBuilder和\@Builder区别说明 254 255当函数componentBuilder被\@Builder修饰时,显示效果为“Child”;当函数componentBuilder被\@LocalBuilder修饰时,显示效果是“Parent”。 256说明: 257 258\@Builder componentBuilder()通过this.componentBuilder的形式传给子组件\@BuilderParam customBuilderParam,this指向子组件Child的实例。 259 260\@LocalBuilder componentBuilder()通过this.componentBuilder的形式传给子组件\@BuilderParam customBuilderParam,this指向父组件Parent的实例。 261 262```ts 263@Component 264struct Child { 265 label: string = 'Child'; 266 @BuilderParam customBuilderParam: () => void; 267 268 build() { 269 Column() { 270 this.customBuilderParam() 271 } 272 } 273} 274 275@Entry 276@Component 277struct Parent { 278 label: string = 'Parent'; 279 280 @Builder componentBuilder() { 281 Text(`${this.label}`) 282 } 283 284 // @LocalBuilder componentBuilder() { 285 // Text(`${this.label}`) 286 // } 287 288 build() { 289 Column() { 290 Child({ customBuilderParam: this.componentBuilder }) 291 } 292 } 293} 294``` 295 296## 使用场景 297 298### \@LocalBuilder在\@ComponentV2修饰的自定义组件中使用 299 300在@ComponentV2装饰的自定义组件中使用局部的@LocalBuilder,修改变量时会触发UI刷新。 301 302```ts 303@ObservedV2 304class Info { 305 @Trace name: string = ''; 306 @Trace age: number = 0; 307} 308 309@ComponentV2 310struct ChildPage { 311 @Require @Param childInfo: Info; 312 313 build() { 314 Column() { 315 Text(`自定义组件 name :${this.childInfo.name}`) 316 .fontSize(20) 317 .fontWeight(FontWeight.Bold) 318 Text(`自定义组件 age :${this.childInfo.age}`) 319 .fontSize(20) 320 .fontWeight(FontWeight.Bold) 321 } 322 } 323} 324 325@Entry 326@ComponentV2 327struct ParentPage { 328 info1: Info = { name: 'Tom', age: 25 }; 329 @Local info2: Info = { name: 'Tom', age: 25 }; 330 331 @LocalBuilder 332 privateBuilder() { 333 Column() { 334 Text(`局部LocalBuilder@Builder name :${this.info1.name}`) 335 .fontSize(20) 336 .fontWeight(FontWeight.Bold) 337 Text(`局部LocalBuilder@Builder age :${this.info1.age}`) 338 .fontSize(20) 339 .fontWeight(FontWeight.Bold) 340 } 341 } 342 343 @LocalBuilder 344 privateBuilderSecond() { 345 Column() { 346 Text(`局部LocalBuilder@Builder name :${this.info2.name}`) 347 .fontSize(20) 348 .fontWeight(FontWeight.Bold) 349 Text(`局部LocalBuilder@Builder age :${this.info2.age}`) 350 .fontSize(20) 351 .fontWeight(FontWeight.Bold) 352 } 353 } 354 355 build() { 356 Column() { 357 Text(`info1: ${this.info1.name} ${this.info1.age}`) // Text1 358 .fontSize(30) 359 .fontWeight(FontWeight.Bold) 360 this.privateBuilder() // 调用局部@Builder 361 Line() 362 .width('100%') 363 .height(10) 364 .backgroundColor('#000000').margin(10) 365 Text(`info2: ${this.info2.name} ${this.info2.age}`) // Text2 366 .fontSize(30) 367 .fontWeight(FontWeight.Bold) 368 this.privateBuilderSecond() // 调用局部@Builder 369 Line() 370 .width('100%') 371 .height(10) 372 .backgroundColor('#000000').margin(10) 373 Text(`info1: ${this.info1.name} ${this.info1.age}`) // Text1 374 .fontSize(30) 375 .fontWeight(FontWeight.Bold) 376 ChildPage({ childInfo: this.info1 }) // 调用自定义组件 377 Line() 378 .width('100%') 379 .height(10) 380 .backgroundColor('#000000').margin(10) 381 Text(`info2: ${this.info2.name} ${this.info2.age}`) // Text2 382 .fontSize(30) 383 .fontWeight(FontWeight.Bold) 384 ChildPage({ childInfo: this.info2 }) // 调用自定义组件 385 Line() 386 .width('100%') 387 .height(10) 388 .backgroundColor('#000000').margin(10) 389 Button('change info1&info2') 390 .onClick(() => { 391 this.info1 = { name: 'Cat', age: 18 }; // Text1不会刷新,原因是info1没被装饰器装饰,无法监听到值的改变。 392 this.info2 = { name: 'Cat', age: 18 }; // Text2会刷新,原因是info2有装饰器装饰,可以监听到值的改变。 393 }) 394 } 395 } 396} 397```