1# \@LocalBuilder装饰器: 维持组件父子关系
2
3当开发者使用@Builder做引用数据传递时,会考虑组件的父子关系,使用了bind(this)之后,组件的父子关系和状态管理的父子关系并不一致。为了解决组件的父子关系和状态管理的父子关系保持一致,引入@LocalBuilder装饰器。@LocalBuilder拥有和局部@Builder相同的功能,且比局部@Builder能够更好的确定组件的父子关系和状态管理的父子关系。
4
5
6> **说明:**
7>
8> 从API version 12开始支持。
9>
10> 
11
12## 装饰器使用说明
13
14
15### 自定义组件内自定义构建函数
16
17定义的语法:
18
19
20```ts
21@LocalBuilder MyBuilderFunction() { ... }
22```
23
24使用方法:
25
26
27```ts
28this.MyBuilderFunction()
29```
30
31- 允许在自定义组件内定义一个或多个@LocalBuilder方法,该方法被认为是该组件的私有、特殊类型的成员函数。
32- 自定义构建函数可以在所属组件的build方法和其他自定义构建函数中调用,但不允许在组件外调用。
33- 在自定义函数体中,this指代当前所属组件,组件的状态变量可以在自定义构建函数内访问。建议通过this访问自定义组件的状态变量而不是参数传递。
34
35## 限制条件
36
37- @LocalBuilder只能在所属组件内声明,不允许全局声明。
38
39- @LocalBuilder不能被内置装饰器和自定义装饰器使用。
40
41- 自定义组件内的静态方法不能和@LocalBuilder一起使用。
42
43## @LocalBuilder和局部@Builder使用区别
44
45@Builder方法引用传参时,为了改变this指向,使用bind(this)后,会导致组件的父子关系和状态管理的父子关系不一致,但是@LocalBuilder是否使用bind(this),都不会改变组件的父子关系。[@LocalBuilder和@Builder区别说明](arkts-localBuilder.md#localbuilder和builder区别说明)。
46
47## 参数传递规则
48
49@LocalBuilder函数的参数传递有[按值传递](#按值传递参数)和[按引用传递](#按引用传递参数)两种,均需遵守以下规则:
50
51- 参数的类型必须与参数声明的类型一致,不允许undefined、null和返回undefined、null的表达式。
52
53- 在@LocalBuilder修饰的函数内部,不允许改变参数值。
54
55- \@LocalBuilder内UI语法遵循[UI语法规则](arkts-create-custom-components.md#build函数)。
56
57- 只有传入一个参数,且参数需要直接传入对象字面量才会按引用传递该参数,其余传递方式均为按值传递。
58
59
60### 按引用传递参数
61
62按引用传递参数时,传递的参数可为状态变量,且状态变量的改变会引起\@LocalBuilder方法内的UI刷新。
63
64使用场景:
65
66组件Parent内的@LocalBuilder方法在build函数内调用,按键值对写法进行传值,当点击Click me 时,@LocalBuilder内的Text文本内容会随着状态变量内容的改变而改变。
67
68```ts
69class ReferenceType {
70  paramString: string = '';
71}
72
73@Entry
74@Component
75struct Parent {
76  @State variableValue: string = 'Hello World';
77
78  @LocalBuilder
79  citeLocalBuilder(params: ReferenceType) {
80    Row() {
81      Text(`UseStateVarByReference: ${params.paramString} `)
82    }
83  };
84
85  build() {
86    Column() {
87      this.citeLocalBuilder({ paramString: this.variableValue });
88      Button('Click me').onClick(() => {
89        this.variableValue = 'Hi World';
90      })
91    }
92  }
93}
94```
95
96按引用传递参数时,如果在\@LocalBuilder方法内调用自定义组件,ArkUI提供[$$](arkts-two-way-sync.md)作为按引用传递参数的范式。
97
98使用场景:
99
100组件Parent内的@LocalBuilder方法内调用自定义组件,且按照引用传递参数将值传递到自定义组件,当Parent组件内状态变量值发生变化时,@LocalBuilder方法内的自定义组件HelloComponent的message值也会发生变化。
101
102```ts
103class ReferenceType {
104  paramString: string = '';
105}
106
107@Component
108struct HelloComponent {
109  @Prop message: string;
110
111  build() {
112    Row() {
113      Text(`HelloComponent===${this.message}`);
114    }
115  }
116}
117
118@Entry
119@Component
120struct Parent {
121  @State variableValue: string = 'Hello World';
122
123  @LocalBuilder
124  citeLocalBuilder($$: ReferenceType) {
125    Row() {
126      Column() {
127        Text(`citeLocalBuilder===${$$.paramString}`);
128        HelloComponent({ message: $$.paramString });
129      }
130    }
131  }
132
133  build() {
134    Column() {
135      this.citeLocalBuilder({ paramString: this.variableValue });
136      Button('Click me').onClick(() => {
137        this.variableValue = 'Hi World';
138      })
139    }
140  }
141}
142```
143
144
145### 按值传递参数
146
147调用\@LocalBuilder装饰的函数默认按值传递。当传递的参数为状态变量时,状态变量的改变不会引起\@LocalBuilder方法内的UI刷新。所以当使用状态变量的时候,推荐使用[按引用传递](#按引用传递参数)。
148
149使用场景:
150
151组件Parent将@State修饰的label值按照函数传参方式传递到@LocalBuilder函数内,此时@LocalBuilder函数获取到的值为普通变量值,所以改变@State修饰的label值时,@LocalBuilder函数内的值不会发生改变。
152
153
154```ts
155@Entry
156@Component
157struct Parent {
158  @State label: string = 'Hello';
159
160  @LocalBuilder
161  citeLocalBuilder(paramA1: string) {
162    Row() {
163      Text(`UseStateVarByValue: ${paramA1} `)
164    }
165  }
166
167  build() {
168    Column() {
169      this.citeLocalBuilder(this.label);
170    }
171  }
172}
173```
174
175## @LocalBuilder和@Builder区别说明
176
177函数componentBuilder被@Builder修饰时,显示效果是 “Child”,函数componentBuilder被@LocalBuilder修饰时,显示效果是“Parent”。
178
179说明:
180
181@Builder componentBuilder()通过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向在Child的label,即“Child”。
182
183@LocalBuilder componentBuilder()通过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向Parent的label,即“Parent”。
184
185```ts
186@Component
187struct Child {
188  label: string = `Child`;
189  @BuilderParam customBuilderParam: () => void;
190
191  build() {
192    Column() {
193      this.customBuilderParam()
194    }
195  }
196}
197
198@Entry
199@Component
200struct Parent {
201  label: string = `Parent`;
202
203  @Builder componentBuilder() {
204    Text(`${this.label}`)
205  }
206
207  // @LocalBuilder componentBuilder() {
208  //   Text(`${this.label}`)
209  // }
210
211  build() {
212    Column() {
213      Child({ customBuilderParam: this.componentBuilder })
214    }
215  }
216}
217```
218
219## 使用场景
220
221### @LocalBuilder在@ComponentV2修饰的自定义组件中使用
222
223使用局部的@LocalBuilder在@ComponentV2修饰的自定义组件中调用,修改变量触发UI刷新。
224
225```ts
226@ObservedV2
227class Info {
228  @Trace name: string = '';
229  @Trace age: number = 0;
230}
231
232@ComponentV2
233struct ChildPage {
234  @Require @Param childInfo: Info;
235  build() {
236    Column() {
237      Text(`自定义组件 name :${this.childInfo.name}`)
238        .fontSize(20)
239        .fontWeight(FontWeight.Bold)
240      Text(`自定义组件 age :${this.childInfo.age}`)
241        .fontSize(20)
242        .fontWeight(FontWeight.Bold)
243    }
244  }
245}
246
247@Entry
248@ComponentV2
249struct ParentPage {
250  info1: Info = { name: "Tom", age: 25 };
251  @Local info2: Info = { name: "Tom", age: 25 };
252
253  @LocalBuilder
254  privateBuilder() {
255    Column() {
256      Text(`局部LocalBuilder@Builder name :${this.info1.name}`)
257        .fontSize(20)
258        .fontWeight(FontWeight.Bold)
259      Text(`局部LocalBuilder@Builder age :${this.info1.age}`)
260        .fontSize(20)
261        .fontWeight(FontWeight.Bold)
262    }
263  }
264
265  @LocalBuilder
266  privateBuilderSecond() {
267    Column() {
268      Text(`局部LocalBuilder@Builder name :${this.info2.name}`)
269        .fontSize(20)
270        .fontWeight(FontWeight.Bold)
271      Text(`局部LocalBuilder@Builder age :${this.info2.age}`)
272        .fontSize(20)
273        .fontWeight(FontWeight.Bold)
274    }
275  }
276  build() {
277    Column() {
278      Text(`info1: ${this.info1.name}  ${this.info1.age}`) // Text1
279        .fontSize(30)
280        .fontWeight(FontWeight.Bold)
281      this.privateBuilder() // 调用局部@Builder
282      Line()
283        .width('100%')
284        .height(10)
285        .backgroundColor('#000000').margin(10)
286      Text(`info2: ${this.info2.name}  ${this.info2.age}`) // Text2
287        .fontSize(30)
288        .fontWeight(FontWeight.Bold)
289      this.privateBuilderSecond() // 调用局部@Builder
290      Line()
291        .width('100%')
292        .height(10)
293        .backgroundColor('#000000').margin(10)
294      Text(`info1: ${this.info1.name}  ${this.info1.age}`) // Text1
295        .fontSize(30)
296        .fontWeight(FontWeight.Bold)
297      ChildPage({ childInfo: this.info1}) // 调用自定义组件
298      Line()
299        .width('100%')
300        .height(10)
301        .backgroundColor('#000000').margin(10)
302      Text(`info2: ${this.info2.name}  ${this.info2.age}`) // Text2
303        .fontSize(30)
304        .fontWeight(FontWeight.Bold)
305      ChildPage({ childInfo: this.info2}) // 调用自定义组件
306      Line()
307        .width('100%')
308        .height(10)
309        .backgroundColor('#000000').margin(10)
310      Button("change info1&info2")
311        .onClick(() => {
312          this.info1 = { name: "Cat", age: 18} // Text1不会刷新,原因是没有装饰器修饰监听不到值的改变。
313          this.info2 = { name: "Cat", age: 18} // Text2会刷新,原因是有装饰器修饰,可以监听到值的改变。
314        })
315    }
316  }
317}
318```