• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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