1# \@LocalBuilder Decorator: Maintaining Component Relationships 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @zhangboren--> 5<!--Designer: @zhangboren--> 6<!--Tester: @TerryTsao--> 7<!--Adviser: @zhang_yixin13--> 8 9The @LocalBuilder decorator addresses a critical challenge in component composition. While local \@Builder functions enable reference data passing between components, they require careful management of parent-child relationships. Specifically, when you use **.bind(this)** to modify function call contexts, this often leads to misalignment between the visual component hierarchy and the underlying state management relationships. To address this issue, the @LocalBuilder decorator is introduced. @LocalBuilder provides the same functionality as the local @Builder but better ensures consistency between the visual component hierarchy and the underlying state management relationships. 10 11Before proceeding, you are advised to review [\@Builder](./arkts-builder.md) to understand the base functionality that \@LocalBuilder enhances. 12 13> **NOTE** 14> 15> This decorator is supported since API version 12. 16> 17> This decorator is supported in ArkTS widgets since API version 12. 18> 19> This decorator can be used in atomic services since API version 12. 20 21## How to Use 22 23### Component-Level Builder Declaration 24 25Syntax definition: 26 27```ts 28@LocalBuilder myBuilderFunction() { ... } 29``` 30 31Invocation pattern: 32 33```ts 34this.myBuilderFunction() 35``` 36- One or more @LocalBuilder functions can be defined in a custom component. These functions are treated as private and special member functions of the owning component. 37 38- Each \@LocalBuilder function is accessible only within the owning component's scope and can be called from the component's **build** method or other local builder functions. 39 40- 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. 41 42## Constraints 43 44- @LocalBuilder declarations are component-bound. Global declarations are not allowed. 45 46- \@LocalBuilder cannot be used in conjunction with other decorators, whether built-in or custom. 47 48- \@LocalBuilder cannot be used to decorate static functions in custom components. 49 50## Differences Between \@LocalBuilder and Local \@Builder 51 52When local @Builder functions are passed between components, it is common to use **.bind(this)** to modify the function context. However, this approach may lead to inconsistencies between the visual component hierarchy and the underlying state management relationships. In contrast, @LocalBuilder maintains stable parent-child relationships regardless of **.bind(this)** usage. This means that the parent component of elements defined in @LocalBuilder remains fixed and immutable. For details, see [Comparison Between @LocalBuilder and @Builder](arkts-localBuilder.md#comparison-between-localbuilder-and-builder). 53 54 55 56> **NOTE** 57> 58> The **bind()** method creates a new bound function that sets the **this** value to the first parameter provided during creation. 59 60## Parameter Passing Rules 61 62@LocalBuilder functions support two parameter passing modes: [pass by value](#by-value-parameter-passing) and [pass by reference](#by-reference-parameter-passing). Both modes must comply with the following rules: 63 64- Parameter types must exactly match their declared types. **undefined** and **null** values are prohibited. 65 66- All parameters must be immutable inside the \@LocalBuilder function body. 67 68- The \@LocalBuilder function body follows the same [syntax rules](arkts-create-custom-components.md#build) as the **build()** function. 69 70- Object literals are passed by reference. All other types of parameters are passed by value. 71 72### By-Reference Parameter Passing 73 74In by-reference parameter passing, state variables can be passed, and the change of these state variables causes the UI re-rendering in the \@LocalBuilder function. 75 76> **NOTE** 77> 78> If the \@LocalBuilder function is used together with the **$$** operator, when a child component calls the @LocalBuilder function of the parent component, parameter changes from the child component will not trigger UI re-rendering in the @LocalBuilder function. 79 80In the example, the @LocalBuilder function in the **Parent** component is called in the **build** function using the key-value pair syntax for parameter passing. After **Click me** is clicked, the **Text** content in the @LocalBuilder changes with the state variable. 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 110For passing parameters by reference in an \@LocalBuilder function that invokes custom components, ArkUI provides [$$](arkts-two-way-sync.md) as the standard paradigm. 111 112In the following example, in the \@LocalBuilder function of the **Parent** component, a custom component is called with parameters passed by reference. When the value of the state variable in the **Parent** component changes, the **message** value of the custom component **HelloComponent** within the @LocalBuilder function will also change. 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 156When a child component calls the @LocalBuilder function of its parent component with state variables, changes to the state variable do not trigger UI re-rendering within the @LocalBuilder function. This occurs because components created using the \@LocalBuilder function remain bound to the parent, and the state variable updates only affect the current component and its children, not the parent. In contrast, using \@Builder functions can trigger UI re-rendering. This is because \@Builder dynamically changes the function's **this** context to point to the calling child component, binding the created components to the child instead of the parent component. 157 158The following example exhibits the distinct behaviors between @Builder and @LocalBuilder. When the **Child** component passes state variables to the **Parent** component's @Builder and @LocalBuilder functions: Inside \@Builder, **this** points to **Child**, and parameter changes trigger UI re-rendering. Inside \@LocalBuilder, **this** points to **Parent**, and parameter changes do not trigger UI re-rendering. Only changes to the Parent component's own state variables will cause the UI to re-render. 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### By-Value Parameter Passing 227 228By default, parameters in the \@LocalBuilder functions are passed by value. In this case, when the passed parameter is a state variable, its change does not cause UI re-rendering in the \@LocalBuilder function. Therefore, for state variables, you are advised to use [by-reference parameter passing](#by-reference-parameter-passing) instead. 229 230In the following example, the **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 regular variable value. As a result, when the **label** value changes, the value in the @LocalBuilder function remains unchanged. 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## Comparison Between \@LocalBuilder and \@Builder 254 255When 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. 256Key points: 257 258For the \@Builder decorated function, when the parent component passes **this.componentBuilder** to the child component's **@BuilderParam customBuilderParam**, the **this** context points to the child component instance (**Child**). 259 260For the \@LocalBuilder decorated function, when the parent component passes **this.componentBuilder** to the child component's **@BuilderParam customBuilderParam**, the **this** context points to the parent component instance (**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## Use Cases 297 298### Using \@LocalBuilder in \@ComponentV2 Decorated Custom Components 299 300When @LocalBuilder is used within @ComponentV2 decorated custom components, UI re-rendering is triggered upon variable changes. 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(`Custom component name: ${this.childInfo.name}`) 316 .fontSize(20) 317 .fontWeight(FontWeight.Bold) 318 Text(`Custom component 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(`Local @LocalBuilder name: ${this.info1.name}`) 335 .fontSize(20) 336 .fontWeight(FontWeight.Bold) 337 Text(`Local @LocalBuilder age: ${this.info1.age}`) 338 .fontSize(20) 339 .fontWeight(FontWeight.Bold) 340 } 341 } 342 343 @LocalBuilder 344 privateBuilderSecond() { 345 Column() { 346 Text(`Local @LocalBuilder name: ${this.info2.name}`) 347 .fontSize(20) 348 .fontWeight(FontWeight.Bold) 349 Text(`Local @LocalBuilder 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() // Call the local @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() // Call the local @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 }) // Call the custom component. 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 }) // Call the custom component. 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 will not be re-rendered because no decorator is used to listen for changes to info1. 392 this.info2 = { name: 'Cat', age: 18 }; // Text2 will be re-rendered because a decorator is used to listen for changes to info2. 393 }) 394 } 395 } 396} 397``` 398