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