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