1# \@LocalBuilder装饰器: 维持组件父子关系 2 3当开发者使用@Builder做引用数据传递时,会考虑组件的父子关系,使用了bind(this)之后,组件的父子关系和状态管理的父子关系并不一致。为了解决组件的父子关系和状态管理的父子关系保持一致的问题,引入@LocalBuilder装饰器。@LocalBuilder拥有和局部@Builder相同的功能,且比局部@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)后,会导致组件的父子关系和状态管理的父子关系不一致,但是@LocalBuilder是否使用bind(this),都不会改变组件的父子关系。[@LocalBuilder和@Builder区别说明](arkts-localBuilder.md#localbuilder和builder区别说明)。 47 48## 参数传递规则 49 50@LocalBuilder函数的参数传递有[按值传递](#按值传递参数)和[按引用传递](#按引用传递参数)两种,均需遵守以下规则: 51 52- 参数的类型必须与参数声明的类型一致,不允许undefined、null和返回undefined、null的表达式。 53 54- 在@LocalBuilder修饰的函数内部,不允许改变参数值。 55 56- \@LocalBuilder内UI语法遵循[UI语法规则](arkts-create-custom-components.md#build函数)。 57 58- 只有传入一个参数,且参数需要直接传入对象字面量才会按引用传递该参数,其余传递方式均为按值传递。 59 60 61### 按引用传递参数 62 63按引用传递参数时,传递的参数可为状态变量,且状态变量的改变会引起\@LocalBuilder方法内的UI刷新。 64 65特别说明,若\@LocalBuilder函数和$$参数一起使用,子组件调用父组件的@LocalBuilder函数,传入的参数发生变化,不会引起\@LocalBuilder方法内的UI刷新。 66 67使用场景: 68 69组件Parent内的@LocalBuilder方法在build函数内调用,按键值对写法进行传值,当点击Click me 时,@LocalBuilder内的Text文本内容会随着状态变量内容的改变而改变。 70 71```ts 72class ReferenceType { 73 paramString: string = ''; 74} 75 76@Entry 77@Component 78struct Parent { 79 @State variableValue: string = 'Hello World'; 80 81 @LocalBuilder 82 citeLocalBuilder(params: ReferenceType) { 83 Row() { 84 Text(`UseStateVarByReference: ${params.paramString}`) 85 } 86 }; 87 88 build() { 89 Column() { 90 this.citeLocalBuilder({ paramString: this.variableValue }) 91 Button('Click me').onClick(() => { 92 this.variableValue = 'Hi World'; 93 }) 94 } 95 } 96} 97``` 98 99按引用传递参数时,如果在\@LocalBuilder方法内调用自定义组件,ArkUI提供[$$](arkts-two-way-sync.md)作为按引用传递参数的范式。 100 101使用场景: 102 103组件Parent内的@LocalBuilder方法内调用自定义组件,且按照引用传递参数将值传递到自定义组件,当Parent组件内状态变量值发生变化时,@LocalBuilder方法内的自定义组件HelloComponent的message值也会发生变化。 104 105```ts 106class ReferenceType { 107 paramString: string = ''; 108} 109 110@Component 111struct HelloComponent { 112 @Prop message: string; 113 114 build() { 115 Row() { 116 Text(`HelloComponent===${this.message}`) 117 } 118 } 119} 120 121@Entry 122@Component 123struct Parent { 124 @State variableValue: string = 'Hello World'; 125 126 @LocalBuilder 127 citeLocalBuilder($$: ReferenceType) { 128 Row() { 129 Column() { 130 Text(`citeLocalBuilder===${$$.paramString}`) 131 HelloComponent({ message: $$.paramString }) 132 } 133 } 134 } 135 136 build() { 137 Column() { 138 this.citeLocalBuilder({ paramString: this.variableValue }) 139 Button('Click me').onClick(() => { 140 this.variableValue = 'Hi World'; 141 }) 142 } 143 } 144} 145``` 146 147子组件引用父组件的@LocalBuilder函数,传入的参数为状态变量,状态变量的改变不会引发@LocalBuilder方法内的UI刷新,原因是@Localbuilder装饰的函数绑定在父组件上,状态变量刷新机制是刷新本组件以及其子组件,对父组件无影响,故无法引发刷新。若使用@Builder修饰则可引发刷新,原因是@Builder改变了函数的this指向,此时函数被绑定到子组件上,故能引发UI刷新。 148 149 150使用场景: 151 152组件Child将状态变量传递到Parent的@Builder和@LocalBuilder函数内,在@Builder的函数内,this指向Child,参数变化能引发UI刷新,在@LocalBuilder函数内,this指向Parent,参数变化不能引发UI刷新。若@LocalBuilder函数内引用Parent的状态变量发生变化,UI能正常刷新。 153 154```ts 155class Data { 156 size: number = 0; 157} 158 159@Entry 160@Component 161struct Parent { 162 label: string = 'parent'; 163 @State data: Data = new Data(); 164 165 @Builder 166 componentBuilder($$: Data) { 167 Text(`builder + $$`) 168 Text(`${'this -> ' + this.label}`) 169 Text(`${'size : ' + $$.size}`) 170 Text(`------------------------`) 171 } 172 173 @LocalBuilder 174 componentLocalBuilder($$: Data) { 175 Text(`LocalBuilder + $$ data`) 176 Text(`${'this -> ' + this.label}`) 177 Text(`${'size : ' + $$.size}`) 178 Text(`------------------------`) 179 } 180 181 @LocalBuilder 182 contentLocalBuilderNoArgument() { 183 Text(`LocalBuilder + local data`) 184 Text(`${'this -> ' + this.label}`) 185 Text(`${'size : ' + this.data.size}`) 186 Text(`------------------------`) 187 } 188 189 build() { 190 Column() { 191 Child({ 192 contentBuilder: this.componentBuilder, 193 contentLocalBuilder: this.componentLocalBuilder, 194 contentLocalBuilderNoArgument: this.contentLocalBuilderNoArgument, 195 data: this.data 196 }) 197 } 198 } 199} 200 201@Component 202struct Child { 203 label: string = 'child'; 204 @Builder customBuilder() {}; 205 @BuilderParam contentBuilder: ((data: Data) => void) = this.customBuilder; 206 @BuilderParam contentLocalBuilder: ((data: Data) => void) = this.customBuilder; 207 @BuilderParam contentLocalBuilderNoArgument: (() => void) = this.customBuilder; 208 @Link data: Data; 209 210 build() { 211 Column() { 212 this.contentBuilder({ size: this.data.size }) 213 this.contentLocalBuilder({ size: this.data.size }) 214 this.contentLocalBuilderNoArgument() 215 Button("add child size").onClick(() => { 216 this.data.size += 1; 217 }) 218 } 219 } 220} 221``` 222 223### 按值传递参数 224 225调用\@LocalBuilder装饰的函数默认按值传递。当传递的参数为状态变量时,状态变量的改变不会引起\@LocalBuilder方法内的UI刷新。所以当使用状态变量的时候,推荐使用[按引用传递](#按引用传递参数)。 226 227使用场景: 228 229组件Parent将@State修饰的label值按照函数传参方式传递到@LocalBuilder函数内,此时@LocalBuilder函数获取到的值为普通变量值,所以改变@State修饰的label值时,@LocalBuilder函数内的值不会发生改变。 230 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 259@Builder componentBuilder()通过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向在Child的label,即“Child”。 260 261@LocalBuilder componentBuilder()通过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向Parent的label,即“Parent”。 262 263```ts 264@Component 265struct Child { 266 label: string = 'Child'; 267 @BuilderParam customBuilderParam: () => void; 268 269 build() { 270 Column() { 271 this.customBuilderParam() 272 } 273 } 274} 275 276@Entry 277@Component 278struct Parent { 279 label: string = 'Parent'; 280 281 @Builder componentBuilder() { 282 Text(`${this.label}`) 283 } 284 285 // @LocalBuilder componentBuilder() { 286 // Text(`${this.label}`) 287 // } 288 289 build() { 290 Column() { 291 Child({ customBuilderParam: this.componentBuilder }) 292 } 293 } 294} 295``` 296 297## 使用场景 298 299### @LocalBuilder在@ComponentV2修饰的自定义组件中使用 300 301使用局部的@LocalBuilder在@ComponentV2修饰的自定义组件中调用,修改变量触发UI刷新。 302 303```ts 304@ObservedV2 305class Info { 306 @Trace name: string = ''; 307 @Trace age: number = 0; 308} 309 310@ComponentV2 311struct ChildPage { 312 @Require @Param childInfo: Info; 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 build() { 355 Column() { 356 Text(`info1: ${this.info1.name} ${this.info1.age}`) // Text1 357 .fontSize(30) 358 .fontWeight(FontWeight.Bold) 359 this.privateBuilder() // 调用局部@Builder 360 Line() 361 .width('100%') 362 .height(10) 363 .backgroundColor('#000000').margin(10) 364 Text(`info2: ${this.info2.name} ${this.info2.age}`) // Text2 365 .fontSize(30) 366 .fontWeight(FontWeight.Bold) 367 this.privateBuilderSecond() // 调用局部@Builder 368 Line() 369 .width('100%') 370 .height(10) 371 .backgroundColor('#000000').margin(10) 372 Text(`info1: ${this.info1.name} ${this.info1.age}`) // Text1 373 .fontSize(30) 374 .fontWeight(FontWeight.Bold) 375 ChildPage({ childInfo: this.info1}) // 调用自定义组件 376 Line() 377 .width('100%') 378 .height(10) 379 .backgroundColor('#000000').margin(10) 380 Text(`info2: ${this.info2.name} ${this.info2.age}`) // Text2 381 .fontSize(30) 382 .fontWeight(FontWeight.Bold) 383 ChildPage({ childInfo: this.info2}) // 调用自定义组件 384 Line() 385 .width('100%') 386 .height(10) 387 .backgroundColor('#000000').margin(10) 388 Button("change info1&info2") 389 .onClick(() => { 390 this.info1 = { name: "Cat", age: 18} // Text1不会刷新,原因是没有装饰器修饰监听不到值的改变。 391 this.info2 = { name: "Cat", age: 18} // Text2会刷新,原因是有装饰器修饰,可以监听到值的改变。 392 }) 393 } 394 } 395} 396```