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```