1# \@LocalBuilder Decorator: Maintaining the Parent-Child Relationship Between Component and State Management 2 3When use @Builder to pass data, the parent-child relationship of components is considered. After **bind(this)** is used, the parent-child relationship of components is inconsistent with that of state management. As a result, the @LocalBuilder decorator is used to fix the inconsistency. @LocalBuilder has the same features as local @Builder and provides a better determination of the parent-child relationship of components and state management. 4 5Before reading this topic, you are advised to read [\@Builder](./arkts-builder.md). 6 7> **NOTE** 8> 9> This decorator is supported since API version 12. 10> 11 12## How to Use 13 14 15### Local Custom Builder Function 16 17Syntax: 18 19 20```ts 21@LocalBuilder MyBuilderFunction() { ... } 22``` 23 24Usage: 25 26 27```ts 28this.MyBuilderFunction() 29``` 30 31- One or more @LocalBuilder methods can be defined in a custom component. The methods are considered as private and special member functions of the component. 32- The custom builder function can be called from the **build** method or another custom builder function in the same component only. 33- Inside the custom builder function body, **this** refers to the owning component. Component state variables are accessible from within the custom builder function implementation. Using **this** to access the custom components' state variables is recommended over parameter passing. 34 35## Constraints 36 37- @LocalBuilder can be declared only within the component to which it belongs. Global declaration is not allowed. 38 39- @LocalBuilder cannot be used by built-in decorators and custom decorators. 40 41- Static methods in a custom component cannot be used together with @LocalBuilder. 42 43## Differences Between @LocalBuilder and Local @Builder 44 45To change the pointed object of **this**, **bind(this)** used in the local @Builder will cause inconsistent parent-child relationship between the component and the state management. However, this problem does not exist in the @LocalBuilder. For details, see [Differences between @LocalBuilder and @Builder](arkts-localBuilder.md#differences-between-localbuilder-and-builder). 46 47## Parameter Passing Rules 48 49For @LocalBuilder functions, parameters can be passed [by value](#by-value-parameter-passing) and [by reference](#by-reference-parameter-passing). Both of them must comply with the following rules: 50 51- The parameter type must be the same as the declared parameter type. The **undefined** or **null** constants as well as expressions evaluating to these values are not allowed. 52 53- All parameters must be immutable inside the @LocalBuilder function. 54 55- The \@LocalBuilder function body follows the same [syntax rules](arkts-create-custom-components.md#build-function) as the **build()** function. 56 57- Parameters are passed by value in all cases except when only one parameter is passed in and the parameter needs to be directly passed to the object literal. 58 59 60### By-Reference Parameter Passing 61 62In by-reference parameter passing, state variables can be passed, and the change of these state variables causes the UI re-rendering in the \@LocalBuilder decorated method. 63 64Note that if the \@LocalBuilder function is used together with the **$$** parameter, the passed parameters change when the child component calls the @LocalBuilder function of the parent component and the UI in \@LocalBuilder is not re-rendered. 65 66Use scenario: 67 68The @LocalBuilder method in the **Parent** component is called in the **build** function to pass the parameters by keys. When you click **Click me**, the **Text** content in the @LocalBuilder changes with the state variable. 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 98When parameters are passed by reference, if a custom component is called within the\@LocalBuilder method, ArkUI provides [$$](arkts-two-way-sync.md) as the paradigm for passing parameters by reference. 99 100Use scenario: 101 102The @LocalBuilder method in the **Parent** component is called in the custom component to pass the parameters by reference. When the value of a state variable in the **Parent** component changes, the **message** of the custom component **HelloComponent** in the @LocalBuilder method also changes. 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 146The child component references the @LocalBuilder function of the parent component with a state variable as the parameter. The change of the state variable does not trigger the UI re-render in the @LocalBuilder method because the function decorated by @Localbuilder is bound to the parent component and only the current component and its child components are re-rendered. Therefore, the re-render of the parent component cannot be triggered. If @Builder is used, the UI can be re-rendered. The reason is that @Builder has the pointer of the **this** keyword changed. In this case, the function is bound to the child component, and the UI can be re-rendered. 147 148 149Use scenario: 150 151The **Child** component passes the state variable to the @Builder and @LocalBuilder functions of the **Parent** component. In the @Builder function, **this** points to the **Child** component and parameter changes can trigger a UI re-rendering. In the @LocalBuilder function, **this** points to the **Parent** component and parameter changes cannot trigger the UI re-rendering. If the state variable of **Parent** referenced in the @LocalBuilder function changes, the UI can be re-rendered properly. 152 153```ts 154class Data { 155 size: number = 0; 156} 157 158@Entry 159@Component 160struct Parent { 161 label: string = 'parent'; 162 @State data: Data = new Data(); 163 164 @Builder 165 componentBuilder($$: Data) { 166 Text(`builder + $$`) 167 Text(`${'this -> ' + this.label}`) 168 Text(`${'size : ' + $$.size}`) 169 Text(`------------------------`) 170 } 171 172 @LocalBuilder 173 componentLocalBuilder($$: Data) { 174 Text(`LocalBuilder + $$ data`) 175 Text(`${'this -> ' + this.label}`) 176 Text(`${'size : ' + $$.size}`) 177 Text(`------------------------`) 178 } 179 180 @LocalBuilder 181 contentLocalBuilderNoArgument() { 182 Text(`LocalBuilder + local data`) 183 Text(`${'this -> ' + this.label}`) 184 Text(`${'size : ' + this.data.size}`) 185 Text(`------------------------`) 186 } 187 188 build() { 189 Column() { 190 Child({ 191 contentBuilder: this.componentBuilder, 192 contentLocalBuilder: this.componentLocalBuilder, 193 contentLocalBuilderNoArgument: this.contentLocalBuilderNoArgument, 194 data: this.data 195 }) 196 } 197 } 198} 199 200@Component 201struct Child { 202 label: string = 'child'; 203 @Builder customBuilder() {}; 204 @BuilderParam contentBuilder: ((data: Data) => void) = this.customBuilder; 205 @BuilderParam contentLocalBuilder: ((data: Data) => void) = this.customBuilder; 206 @BuilderParam contentLocalBuilderNoArgument: (() => void) = this.customBuilder; 207 @Link data: Data; 208 209 build() { 210 Column() { 211 this.contentBuilder({ size: this.data.size }) 212 this.contentLocalBuilder({ size: this.data.size }) 213 this.contentLocalBuilderNoArgument() 214 Button("add child size").onClick(() => { 215 this.data.size += 1; 216 }) 217 } 218 } 219} 220``` 221 222### By-Value Parameter Passing 223 224By default, parameters in the \@LocalBuilder decorated functions are passed by value. In this case, when the passed parameter is a state variable, the change of the state variable does not cause UI re-rendering in the \@LocalBuilder decorated function. Therefore, when passing state variables, you are advised to use [by-reference parameter passing](#by-reference-parameter-passing). 225 226Use scenario: 227 228The **Parent** component passes the @State decorated **label** value to the @LocalBuilder function as a function parameter. In this case, the value obtained by the @LocalBuilder function is a common variable value. Therefore, when the @State decorated **label** value is changed, the value in the @LocalBuilder function does not change. 229 230 231```ts 232@Entry 233@Component 234struct Parent { 235 @State label: string = 'Hello'; 236 237 @LocalBuilder 238 citeLocalBuilder(paramA1: string) { 239 Row() { 240 Text(`UseStateVarByValue: ${paramA1}`) 241 } 242 } 243 244 build() { 245 Column() { 246 this.citeLocalBuilder(this.label) 247 } 248 } 249} 250``` 251 252## Differences Between @LocalBuilder and @Builder 253 254When the **componentBuilder** function is decorated by @Builder, the **Child** component is displayed. When the **componentBuilder** function is decorated by @LocalBuilder, the **Parent** component is displayed. 255 256**NOTE** 257 258**@Builder componentBuilder()** is passed to the child component **@BuilderParam customBuilderParam** in the form of **this.componentBuilder**. **this** points to the label of **Child**, that is, **Child** is displayed. 259 260**@LocalBuilder componentBuilder()** is passed to the child component **@BuilderParam customBuilderParam** in the form of **this.componentBuilder**. **this** points to the label of **Parent**, that is, **Parent** is displayed. 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## Use Scenarios 297 298### Using @LocalBuilder in @ComponentV2 Decorated Custom Components 299 300Call the @LocalBuilder in the custom component decorated by @ComponentV2 to change the variables, triggering the UI re-renders. 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 build() { 313 Column() { 314 Text(`Custom component name :${this.childInfo.name}`) 315 .fontSize(20) 316 .fontWeight(FontWeight.Bold) 317 Text(`Custom component age :${this.childInfo.age}`) 318 .fontSize(20) 319 .fontWeight(FontWeight.Bold) 320 } 321 } 322} 323 324@Entry 325@ComponentV2 326struct ParentPage { 327 info1: Info = { name: "Tom", age: 25 }; 328 @Local info2: Info = { name: "Tom", age: 25 }; 329 330 @LocalBuilder 331 privateBuilder() { 332 Column() { 333 Text(`LocalBuilder@Builder name :${this.info1.name}`) 334 .fontSize(20) 335 .fontWeight(FontWeight.Bold) 336 Text(`LocalBuilder@Builder age :${this.info1.age}`) 337 .fontSize(20) 338 .fontWeight(FontWeight.Bold) 339 } 340 } 341 342 @LocalBuilder 343 privateBuilderSecond() { 344 Column() { 345 Text(`LocalBuilder@Builder name :${this.info2.name}`) 346 .fontSize(20) 347 .fontWeight(FontWeight.Bold) 348 Text(`LocalBuilder@Builder age :${this.info2.age}`) 349 .fontSize(20) 350 .fontWeight(FontWeight.Bold) 351 } 352 } 353 build() { 354 Column() { 355 Text(`info1: ${this.info1.name} ${this.info1.age}`) // Text1 356 .fontSize(30) 357 .fontWeight(FontWeight.Bold) 358 this.privateBuilder() // Call the local @Builder. 359 Line() 360 .width('100%') 361 .height(10) 362 .backgroundColor('#000000').margin(10) 363 Text(`info2: ${this.info2.name} ${this.info2.age}`) // Text2 364 .fontSize(30) 365 .fontWeight(FontWeight.Bold) 366 this.privateBuilderSecond() // Call the local @Builder. 367 Line() 368 .width('100%') 369 .height(10) 370 .backgroundColor('#000000').margin(10) 371 Text(`info1: ${this.info1.name} ${this.info1.age}`) // Text1 372 .fontSize(30) 373 .fontWeight(FontWeight.Bold) 374 ChildPage({childInfo: this.info1}) // Call the custom component. 375 Line() 376 .width('100%') 377 .height(10) 378 .backgroundColor('#000000').margin(10) 379 Text(`info2: ${this.info2.name} ${this.info2.age}`) // Text2 380 .fontSize(30) 381 .fontWeight(FontWeight.Bold) 382 ChildPage({childInfo: this.info2}) // Call the custom component. 383 Line() 384 .width('100%') 385 .height(10) 386 .backgroundColor('#000000').margin(10) 387 Button("change info1&info2") 388 .onClick(() => { 389 this.info1 = { name: "Cat", age: 18} // Text1 is not re-rendered because no decorator is used to listen for value changes. 390 this.info2 = { name: "Cat", age: 18} // Text2 is re-rendered because a decorator is used to listen for value changes. 391 }) 392 } 393 } 394} 395``` 396