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若子组件调用父组件的@LocalBuilder函数,传入的参数发生变化,不会引起\@LocalBuilder方法内的UI刷新。 65 66使用场景: 67 68组件Parent内的@LocalBuilder方法在build函数内调用,按键值对写法进行传值,当点击Click me 时,@LocalBuilder内的Text文本内容会随着状态变量内容的改变而改变。 69 70```ts 71class ReferenceType { 72 paramString: string = ''; 73} 74 75@Entry 76@Component 77struct Parent { 78 @State variableValue: string = 'Hello World'; 79 80 @LocalBuilder 81 citeLocalBuilder(params: ReferenceType) { 82 Row() { 83 Text(`UseStateVarByReference: ${params.paramString} `) 84 } 85 }; 86 87 build() { 88 Column() { 89 this.citeLocalBuilder({ paramString: this.variableValue }); 90 Button('Click me').onClick(() => { 91 this.variableValue = 'Hi World'; 92 }) 93 } 94 } 95} 96``` 97 98按引用传递参数时,如果在\@LocalBuilder方法内调用自定义组件,ArkUI提供[$$](arkts-two-way-sync.md)作为按引用传递参数的范式。 99 100使用场景: 101 102组件Parent内的@LocalBuilder方法内调用自定义组件,且按照引用传递参数将值传递到自定义组件,当Parent组件内状态变量值发生变化时,@LocalBuilder方法内的自定义组件HelloComponent的message值也会发生变化。 103 104```ts 105class ReferenceType { 106 paramString: string = ''; 107} 108 109@Component 110struct HelloComponent { 111 @Prop message: string; 112 113 build() { 114 Row() { 115 Text(`HelloComponent===${this.message}`); 116 } 117 } 118} 119 120@Entry 121@Component 122struct Parent { 123 @State variableValue: string = 'Hello World'; 124 125 @LocalBuilder 126 citeLocalBuilder($$: ReferenceType) { 127 Row() { 128 Column() { 129 Text(`citeLocalBuilder===${$$.paramString}`); 130 HelloComponent({ message: $$.paramString }); 131 } 132 } 133 } 134 135 build() { 136 Column() { 137 this.citeLocalBuilder({ paramString: this.variableValue }); 138 Button('Click me').onClick(() => { 139 this.variableValue = 'Hi World'; 140 }) 141 } 142 } 143} 144``` 145 146子组件引用父组件的@LocalBuilder函数,传入的参数为状态变量,状态变量的改变不会引发@LocalBuilder方法内的UI刷新,原因是@Localbuilder装饰的函数绑定在父组件上,状态变量刷新机制是刷新本组件以及其子组件,对父组件无影响,故无法引发刷新。若使用@Builder修饰则可引发刷新,原因是@Builder改变了函数的this指向,此时函数被绑定到子组件上,故能引发UI刷新。 147 148使用场景: 149 150组件Child将@State修饰的label值按照函数传参方式传递到Parent的@Builder和@LocalBuilder函数内,在被@Builder修饰的函数内,this指向Child,参数变化能引发UI刷新,在被@LocalBuilder修饰的函数内,this指向Parent,参数变化不能引发UI刷新。 151 152 153```ts 154class LayoutSize { 155 size:number = 0; 156} 157 158@Entry 159@Component 160struct Parent { 161 label:string = 'parent'; 162 @State layoutSize:LayoutSize = {size:0}; 163 164 @LocalBuilder 165 // @Builder 166 componentBuilder($$:LayoutSize) { 167 Text(`${'this :'+this.label}`); 168 Text(`${'size :'+$$.size}`); 169 } 170 171 build() { 172 Column() { 173 Child({contentBuilder: this.componentBuilder }); 174 } 175 } 176} 177 178@Component 179struct Child { 180 label:string = 'child'; 181 @BuilderParam contentBuilder:((layoutSize: LayoutSize) => void); 182 @State layoutSize:LayoutSize = {size:0}; 183 184 build() { 185 Column() { 186 this.contentBuilder({size: this.layoutSize.size}); 187 Button("add child size").onClick(()=>{ 188 this.layoutSize.size += 1; 189 }) 190 } 191 } 192} 193``` 194 195使用场景: 196 197组件Child将@Link引用Parent的@State修饰的label值按照函数传参方式传递到Parent的@Builder和@LocalBuilder函数内,在被@Builder修饰的函数内,this指向Child,参数变化能引发UI刷新,在被@LocalBuilder修饰的函数内,this指向Parent,参数变化不能引发UI刷新。 198 199```ts 200class LayoutSize { 201 size:number = 0; 202} 203 204@Entry 205@Component 206struct Parent { 207 label:string = 'parent'; 208 @State layoutSize:LayoutSize = {size:0}; 209 210 @LocalBuilder 211 // @Builder 212 componentBuilder($$:LayoutSize) { 213 Text(`${'this :'+this.label}`); 214 Text(`${'size :'+$$.size}`); 215 } 216 217 build() { 218 Column() { 219 Child({contentBuilder: this.componentBuilder,layoutSize:this.layoutSize}); 220 } 221 } 222} 223 224@Component 225struct Child { 226 label:string = 'child'; 227 @BuilderParam contentBuilder:((layoutSize: LayoutSize) => void); 228 @Link layoutSize:LayoutSize; 229 230 build() { 231 Column() { 232 this.contentBuilder({size: this.layoutSize.size}); 233 Button("add child size").onClick(()=>{ 234 this.layoutSize.size += 1; 235 }) 236 } 237 } 238} 239``` 240 241### 按值传递参数 242 243调用\@LocalBuilder装饰的函数默认按值传递。当传递的参数为状态变量时,状态变量的改变不会引起\@LocalBuilder方法内的UI刷新。所以当使用状态变量的时候,推荐使用[按引用传递](#按引用传递参数)。 244 245使用场景: 246 247组件Parent将@State修饰的label值按照函数传参方式传递到@LocalBuilder函数内,此时@LocalBuilder函数获取到的值为普通变量值,所以改变@State修饰的label值时,@LocalBuilder函数内的值不会发生改变。 248 249 250```ts 251@Entry 252@Component 253struct Parent { 254 @State label: string = 'Hello'; 255 256 @LocalBuilder 257 citeLocalBuilder(paramA1: string) { 258 Row() { 259 Text(`UseStateVarByValue: ${paramA1} `) 260 } 261 } 262 263 build() { 264 Column() { 265 this.citeLocalBuilder(this.label); 266 } 267 } 268} 269``` 270 271## @LocalBuilder和@Builder区别说明 272 273函数componentBuilder被@Builder修饰时,显示效果是 “Child”,函数componentBuilder被@LocalBuilder修饰时,显示效果是“Parent”。 274 275说明: 276 277@Builder componentBuilder()通过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向在Child的label,即“Child”。 278 279@LocalBuilder componentBuilder()通过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向Parent的label,即“Parent”。 280 281```ts 282@Component 283struct Child { 284 label: string = `Child`; 285 @BuilderParam customBuilderParam: () => void; 286 287 build() { 288 Column() { 289 this.customBuilderParam() 290 } 291 } 292} 293 294@Entry 295@Component 296struct Parent { 297 label: string = `Parent`; 298 299 @Builder componentBuilder() { 300 Text(`${this.label}`) 301 } 302 303 // @LocalBuilder componentBuilder() { 304 // Text(`${this.label}`) 305 // } 306 307 build() { 308 Column() { 309 Child({ customBuilderParam: this.componentBuilder }) 310 } 311 } 312} 313``` 314 315## 使用场景 316 317### @LocalBuilder在@ComponentV2修饰的自定义组件中使用 318 319使用局部的@LocalBuilder在@ComponentV2修饰的自定义组件中调用,修改变量触发UI刷新。 320 321```ts 322@ObservedV2 323class Info { 324 @Trace name: string = ''; 325 @Trace age: number = 0; 326} 327 328@ComponentV2 329struct ChildPage { 330 @Require @Param childInfo: Info; 331 build() { 332 Column() { 333 Text(`自定义组件 name :${this.childInfo.name}`) 334 .fontSize(20) 335 .fontWeight(FontWeight.Bold) 336 Text(`自定义组件 age :${this.childInfo.age}`) 337 .fontSize(20) 338 .fontWeight(FontWeight.Bold) 339 } 340 } 341} 342 343@Entry 344@ComponentV2 345struct ParentPage { 346 info1: Info = { name: "Tom", age: 25 }; 347 @Local info2: Info = { name: "Tom", age: 25 }; 348 349 @LocalBuilder 350 privateBuilder() { 351 Column() { 352 Text(`局部LocalBuilder@Builder name :${this.info1.name}`) 353 .fontSize(20) 354 .fontWeight(FontWeight.Bold) 355 Text(`局部LocalBuilder@Builder age :${this.info1.age}`) 356 .fontSize(20) 357 .fontWeight(FontWeight.Bold) 358 } 359 } 360 361 @LocalBuilder 362 privateBuilderSecond() { 363 Column() { 364 Text(`局部LocalBuilder@Builder name :${this.info2.name}`) 365 .fontSize(20) 366 .fontWeight(FontWeight.Bold) 367 Text(`局部LocalBuilder@Builder age :${this.info2.age}`) 368 .fontSize(20) 369 .fontWeight(FontWeight.Bold) 370 } 371 } 372 build() { 373 Column() { 374 Text(`info1: ${this.info1.name} ${this.info1.age}`) // Text1 375 .fontSize(30) 376 .fontWeight(FontWeight.Bold) 377 this.privateBuilder() // 调用局部@Builder 378 Line() 379 .width('100%') 380 .height(10) 381 .backgroundColor('#000000').margin(10) 382 Text(`info2: ${this.info2.name} ${this.info2.age}`) // Text2 383 .fontSize(30) 384 .fontWeight(FontWeight.Bold) 385 this.privateBuilderSecond() // 调用局部@Builder 386 Line() 387 .width('100%') 388 .height(10) 389 .backgroundColor('#000000').margin(10) 390 Text(`info1: ${this.info1.name} ${this.info1.age}`) // Text1 391 .fontSize(30) 392 .fontWeight(FontWeight.Bold) 393 ChildPage({ childInfo: this.info1}) // 调用自定义组件 394 Line() 395 .width('100%') 396 .height(10) 397 .backgroundColor('#000000').margin(10) 398 Text(`info2: ${this.info2.name} ${this.info2.age}`) // Text2 399 .fontSize(30) 400 .fontWeight(FontWeight.Bold) 401 ChildPage({ childInfo: this.info2}) // 调用自定义组件 402 Line() 403 .width('100%') 404 .height(10) 405 .backgroundColor('#000000').margin(10) 406 Button("change info1&info2") 407 .onClick(() => { 408 this.info1 = { name: "Cat", age: 18} // Text1不会刷新,原因是没有装饰器修饰监听不到值的改变。 409 this.info2 = { name: "Cat", age: 18} // Text2会刷新,原因是有装饰器修饰,可以监听到值的改变。 410 }) 411 } 412 } 413} 414```