1# \@Provide装饰器和\@Consume装饰器:与后代组件双向同步
2
3
4\@Provide和\@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,\@Provide和\@Consume摆脱参数传递机制的束缚,实现跨层级传递。
5
6
7其中\@Provide装饰的变量是在祖先组件中,可以理解为被“提供”给后代的状态变量。\@Consume装饰的变量是在后代组件中,去“消费(绑定)”祖先组件提供的变量。
8
9\@Provide/\@Consume是跨组件层级的双向同步。在阅读\@Provide和\@Consume文档前,建议开发者对UI范式基本语法和自定义组件有基本的了解。建议提前阅读:[基本语法概述](./arkts-basic-syntax-overview.md),[声明式UI描述](./arkts-declarative-ui-description.md),[自定义组件-创建自定义组件](./arkts-create-custom-components.md)。
10
11> **说明:**
12>
13> 从API version 9开始,这两个装饰器支持在ArkTS卡片中使用。
14>
15> 从API version 11开始,这两个装饰器支持在原子化服务中使用。
16
17## 概述
18
19\@Provide/\@Consume装饰的状态变量有以下特性:
20
21- \@Provide装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。由此可见,\@Provide的方便之处在于,开发者不需要多次在组件之间传递变量。
22
23- 后代通过使用\@Consume去获取\@Provide提供的变量,建立在\@Provide和\@Consume之间的双向数据同步,与\@State/\@Link不同的是,前者可以在多层级的父子组件之间传递。
24
25- \@Provide和\@Consume可以通过相同的变量名或者相同的变量别名绑定,建议类型相同,否则会发生类型隐式转换,从而导致应用行为异常。
26
27
28```ts
29// 通过相同的变量名绑定
30@Provide age: number = 0;
31@Consume age: number;
32
33// 通过相同的变量别名绑定
34@Provide('a') id: number = 0;
35@Consume('a') age: number;
36```
37
38
39\@Provide和\@Consume通过相同的变量名或者相同的变量别名绑定时,\@Provide装饰的变量和\@Consume装饰的变量是一对多的关系。不允许在同一个自定义组件内,包括其子组件中声明多个同名或者同别名的\@Provide装饰的变量,@Provide的属性名或别名需要唯一且确定,如果声明多个同名或者同别名的@Provide装饰的变量,会发生运行时报错。
40
41
42## 装饰器说明
43
44\@State的规则同样适用于\@Provide,差异为\@Provide还作为多层后代的同步源。
45
46| \@Provide变量装饰器 | 说明                                       |
47| -------------- | ---------------------------------------- |
48| 装饰器参数          | 别名:常量字符串,可选。<br/>如果指定了别名,则通过别名来绑定变量;如果未指定别名,则通过变量名绑定变量。 |
49| 同步类型           | 双向同步。<br/>从\@Provide变量到所有\@Consume变量以及相反的方向的数据同步。双向同步的操作与\@State和\@Link的组合相同。 |
50| 允许装饰的变量类型      | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>支持Date类型。<br/>API11及以上支持Map、Set类型。<br/>支持ArkUI框架定义的联合类型Length、ResourceStr、ResourceColor类型。<br/>必须指定类型。<br/>\@Provide变量的\@Consume变量的类型必须相同。<br/>支持类型的场景请参考[观察变化](#观察变化)。<br/>不支持any。<br/>API11及以上支持上述支持类型的联合类型,比如string \| number, string \| undefined 或者 ClassA \| null,示例见[@Provide_and_Consume支持联合类型实例](#provide_and_consume支持联合类型实例)。 <br/>**注意**<br/>当使用undefined和null的时候,建议显式指定类型,遵循TypeScript类型校验,比如:`@Provide a : string \| undefined = undefined`是推荐的,不推荐`@Provide a: string = undefined`。
51| 被装饰变量的初始值      | 必须指定。                                    |
52| 支持allowOverride参数          | 允许重写,只要声明了allowOverride,则别名和属性名都可以被Override。示例见[\@Provide支持allowOverride参数](#provide支持allowoverride参数)。 |
53
54| \@Consume变量装饰器 | 说明                                       |
55| -------------- | ---------------------------------------- |
56| 装饰器参数          | 别名:常量字符串,可选。<br/>如果提供了别名,则必须有\@Provide的变量和其有相同的别名才可以匹配成功;否则,则需要变量名相同才能匹配成功。 |
57| 同步类型           | 双向:从\@Provide变量(具体请参见\@Provide)到所有\@Consume变量,以及相反的方向。双向同步操作与\@State和\@Link的组合相同。 |
58| 允许装饰的变量类型      | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>支持Date类型。<br/>支持ArkUI框架定义的联合类型Length、ResourceStr、ResourceColor类型。必须指定类型。<br/>\@Provide变量和\@Consume变量的类型必须相同。<br/>\@Consume装饰的变量,在其父组件或者祖先组件上,必须有对应的属性和别名的\@Provide装饰的变量。<br/>支持类型的场景请参考[观察变化](#观察变化)。<br/>不支持any。<br/>API11及以上支持上述支持类型的联合类型,比如string \| number, string \| undefined 或者 ClassA \| null,示例见[@Provide_and_Consume支持联合类型实例](#provide_and_consume支持联合类型实例)。 <br/>**注意**<br/>当使用undefined和null的时候,建议显式指定类型,遵循TypeScript类型校验,比如:`@Consume a : string \| undefined`。
59| 被装饰变量的初始值      | 无,禁止本地初始化。                               |
60
61
62## 变量的传递/访问规则说明
63
64
65| \@Provide传递/访问 | 说明                                       |
66| -------------- | ---------------------------------------- |
67| 从父组件初始化和更新     | 可选,允许父组件中常规变量(常规变量对@Provide赋值,只是数值的初始化,常规变量的变化不会触发UI刷新,只有状态变量才能触发UI刷新)、[\@State](./arkts-state.md)、[\@Link](./arkts-link.md)、[\@Prop](./arkts-prop.md)、\@Provide、\@Consume、[\@ObjectLink](./arkts-observed-and-objectlink.md)、[\@StorageLink](./arkts-appstorage.md#storagelink)、[\@StorageProp](./arkts-appstorage.md#storageprop)、[\@LocalStorageLink](./arkts-localstorage.md#localstoragelink)和[\@LocalStorageProp](./arkts-localstorage.md#localstorageprop)装饰的变量装饰变量初始化子组件\@Provide。 |
68| 用于初始化子组件       | 允许,可用于初始化\@State、\@Link、\@Prop、\@Provide。 |
69| 和父组件同步         | 否。                                       |
70| 和后代组件同步        | 和\@Consume双向同步。                          |
71| 是否支持组件外访问      | 私有,仅可以在所属组件内访问。                          |
72
73
74  **图1** \@Provide初始化规则图示
75
76
77![zh-cn_image_0000001552614217](figures/zh-cn_image_0000001552614217.png)
78
79
80| \@Consume传递/访问 | 说明                                       |
81| -------------- | ---------------------------------------- |
82| 从父组件初始化和更新     | 禁止。通过相同的变量名和alias(别名)从\@Provide初始化。      |
83| 用于初始化子组件       | 允许,可用于初始化\@State、\@Link、\@Prop、\@Provide。 |
84| 和祖先组件同步        | 和\@Provide双向同步。                          |
85| 是否支持组件外访问      | 私有,仅可以在所属组件内访问                           |
86
87
88  **图2** \@Consume初始化规则图示
89
90
91![zh-cn_image_0000001502094666](figures/zh-cn_image_0000001502094666.png)
92
93
94## 观察变化和行为表现
95
96
97### 观察变化
98
99- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
100
101- 当装饰的数据类型为class或者Object的时候,可以观察到赋值和属性赋值的变化(属性为Object.keys(observedObject)返回的所有属性)。
102
103- 当装饰的对象是array的时候,可以观察到数组的添加、删除、更新数组单元。
104
105- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性。
106
107```ts
108@Component
109struct Child {
110  @Consume selectedDate: Date;
111
112  build() {
113    Column() {
114      Button(`child increase the day by 1`)
115        .onClick(() => {
116          this.selectedDate.setDate(this.selectedDate.getDate() + 1)
117        })
118      Button('child update the new date')
119        .margin(10)
120        .onClick(() => {
121          this.selectedDate = new Date('2023-09-09')
122        })
123      DatePicker({
124        start: new Date('1970-1-1'),
125        end: new Date('2100-1-1'),
126        selected: this.selectedDate
127      })
128    }
129  }
130}
131
132@Entry
133@Component
134struct Parent {
135  @Provide selectedDate: Date = new Date('2021-08-08')
136
137  build() {
138    Column() {
139      Button('parent increase the day by 1')
140        .margin(10)
141        .onClick(() => {
142          this.selectedDate.setDate(this.selectedDate.getDate() + 1)
143        })
144      Button('parent update the new date')
145        .margin(10)
146        .onClick(() => {
147          this.selectedDate = new Date('2023-07-07')
148        })
149      DatePicker({
150        start: new Date('1970-1-1'),
151        end: new Date('2100-1-1'),
152        selected: this.selectedDate
153      })
154      Child()
155    }
156  }
157}
158```
159
160- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口`set`, `clear`, `delete` 更新Map的值。详见[装饰Map类型变量](#装饰map类型变量)。
161
162- 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口`add`, `clear`, `delete` 更新Set的值。详见[装饰Set类型变量](#装饰set类型变量)。
163
164### 框架行为
165
1661. 初始渲染:
167   1. \@Provide装饰的变量会以map的形式,传递给当前\@Provide所属组件的所有子组件;
168   2. 子组件中如果使用\@Consume变量,则会在map中查找是否有该变量名/alias(别名)对应的\@Provide的变量,如果查找不到,框架会抛出JS ERROR;
169   3. 在初始化\@Consume变量时,和\@State/\@Link的流程类似,\@Consume变量会在map中查找到对应的\@Provide变量进行保存,并把自己注册给\@Provide。
170
1712. 当\@Provide装饰的数据变化时:
172   1. 通过初始渲染的步骤可知,子组件\@Consume已把自己注册给父组件。父组件\@Provide变量变更后,会遍历更新所有依赖它的系统组件(elementid)和状态变量(\@Consume);
173   2. 通知\@Consume更新后,子组件所有依赖\@Consume的系统组件(elementId)都会被通知更新。以此实现\@Provide对\@Consume状态数据同步。
174
1753. 当\@Consume装饰的数据变化时:
176
177   通过初始渲染的步骤可知,子组件\@Consume持有\@Provide的实例。在\@Consume更新后调用\@Provide的更新方法,将更新的数值同步回\@Provide,以此实现\@Consume向\@Provide的同步更新。
178
179![Provide_Consume_framework_behavior](figures/Provide_Consume_framework_behavior.png)
180
181
182## 限制条件
183
1841. \@Provider/\@Consumer的参数key必须为string类型,否则编译期会报错。
185
186```ts
187// 错误写法,编译报错
188let change: number = 10;
189@Provide(change) message: string = 'Hello';
190
191// 正确写法
192let change: string = 'change';
193@Provide(change) message: string = 'Hello';
194```
195
1962. \@Consume装饰的变量不能本地初始化,也不能在构造参数中传入初始化,否则编译期会报错。\@Consume仅能通过key来匹配对应的\@Provide变量进行初始化。
197
198【反例】
199
200```ts
201@Component
202struct Child {
203  @Consume msg: string;
204  // 错误写法,不允许本地初始化
205  @Consume msg1: string = 'Hello';
206
207  build() {
208    Text(this.msg)
209  }
210}
211
212@Entry
213@Component
214struct Parent {
215  @Provide message: string = 'Hello';
216
217  build() {
218    Column() {
219      // 错误写法,不允许外部传入初始化
220      Child({msg: 'Hello'})
221    }
222  }
223}
224```
225
226【正例】
227
228```ts
229@Component
230struct Child {
231  @Consume num: number;
232
233  build() {
234    Column() {
235      Text(`num的值: ${this.num}`)
236    }
237  }
238}
239
240@Entry
241@Component
242struct Parent {
243  @Provide num: number = 10;
244
245  build() {
246    Column() {
247      Text(`num的值: ${this.num}`)
248      Child()
249    }
250  }
251}
252```
253
2543. \@Provide的key重复定义时,框架会抛出运行时错误,提醒开发者重复定义key,如果开发者需要重复key,可以使用[allowoverride](#provide支持allowoverride参数)。
255
256```ts
257// 错误写法,a重复定义
258@Provide('a') count: number = 10;
259@Provide('a') num: number = 10;
260
261// 正确写法
262@Provide('a') count: number = 10;
263@Provide('b') num: number = 10;
264```
265
2664. 在初始化\@Consume变量时,如果开发者没有定义对应key的\@Provide变量,框架会抛出运行时错误,提示开发者初始化\@Consume变量失败,原因是无法找到其对应key的\@Provide变量。
267
268【反例】
269
270```ts
271@Component
272struct Child {
273  @Consume num: number;
274
275  build() {
276    Column() {
277      Text(`num的值: ${this.num}`)
278    }
279  }
280}
281
282@Entry
283@Component
284struct Parent {
285  // 错误写法,缺少@Provide
286  num: number = 10;
287
288  build() {
289    Column() {
290      Text(`num的值: ${this.num}`)
291      Child()
292    }
293  }
294}
295```
296
297【正例】
298
299```ts
300@Component
301struct Child {
302  @Consume num: number;
303
304  build() {
305    Column() {
306      Text(`num的值: ${this.num}`)
307    }
308  }
309}
310
311@Entry
312@Component
313struct Parent {
314  // 正确写法
315  @Provide num: number = 10;
316
317  build() {
318    Column() {
319      Text(`num的值: ${this.num}`)
320      Child()
321    }
322  }
323}
324```
325
3265. \@Provide与\@Consume不支持装饰Function类型的变量,框架会抛出运行时错误。
327
328
329## 使用场景
330
331在下面的示例是与后代组件双向同步状态\@Provide和\@Consume场景。当分别点击ToDo和ToDoItem组件内Button时,count 的更改会双向同步在ToDo和ToDoItem中。
332
333
334
335```ts
336@Component
337struct ToDoItem {
338  // @Consume装饰的变量通过相同的属性名绑定其祖先组件ToDo内的@Provide装饰的变量
339  @Consume count: number;
340
341  build() {
342    Column() {
343      Text(`count(${this.count})`)
344      Button(`count(${this.count}), count + 1`)
345        .onClick(() => this.count += 1)
346    }
347    .width('50%')
348  }
349}
350
351@Component
352struct ToDoList {
353  build() {
354    Row({ space: 5 }) {
355      ToDoItem()
356      ToDoItem()
357    }
358  }
359}
360
361@Component
362struct ToDoDemo {
363  build() {
364    ToDoList()
365  }
366}
367
368@Entry
369@Component
370struct ToDo {
371  // @Provide装饰的变量index由入口组件ToDo提供其后代组件
372  @Provide count: number = 0;
373
374  build() {
375    Column() {
376      Button(`count(${this.count}), count + 1`)
377        .onClick(() => this.count += 1)
378      ToDoDemo()
379    }
380  }
381}
382```
383
384### 装饰Map类型变量
385
386> **说明:**
387>
388> 从API version 11开始,\@Provide,\@Consume支持Map类型。
389
390在下面的示例中,message类型为Map<number, string>,点击Button改变message的值,视图会随之刷新。
391
392```ts
393@Component
394struct Child {
395  @Consume message: Map<number, string>
396
397  build() {
398    Column() {
399      ForEach(Array.from(this.message.entries()), (item: [number, string]) => {
400        Text(`${item[0]}`).fontSize(30)
401        Text(`${item[1]}`).fontSize(30)
402        Divider()
403      })
404      Button('Consume init map').onClick(() => {
405        this.message = new Map([[0, "a"], [1, "b"], [3, "c"]])
406      })
407      Button('Consume set new one').onClick(() => {
408        this.message.set(4, "d")
409      })
410      Button('Consume clear').onClick(() => {
411        this.message.clear()
412      })
413      Button('Consume replace the first item').onClick(() => {
414        this.message.set(0, "aa")
415      })
416      Button('Consume delete the first item').onClick(() => {
417        this.message.delete(0)
418      })
419    }
420  }
421}
422
423
424@Entry
425@Component
426struct MapSample {
427  @Provide message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]])
428
429  build() {
430    Row() {
431      Column() {
432        Button('Provide init map').onClick(() => {
433          this.message = new Map([[0, "a"], [1, "b"], [3, "c"], [4, "d"]])
434        })
435        Child()
436      }
437      .width('100%')
438    }
439    .height('100%')
440  }
441}
442```
443
444### 装饰Set类型变量
445
446> **说明:**
447>
448> 从API version 11开始,\@Provide,\@Consume支持Set类型。
449
450在下面的示例中,message类型为Set\<number\>,点击Button改变message的值,视图会随之刷新。
451
452```ts
453@Component
454struct Child {
455  @Consume message: Set<number>
456
457  build() {
458    Column() {
459      ForEach(Array.from(this.message.entries()), (item: [number, string]) => {
460        Text(`${item[0]}`).fontSize(30)
461        Divider()
462      })
463      Button('Consume init set').onClick(() => {
464        this.message = new Set([0, 1, 2, 3, 4])
465      })
466      Button('Consume set new one').onClick(() => {
467        this.message.add(5)
468      })
469      Button('Consume clear').onClick(() => {
470        this.message.clear()
471      })
472      Button('Consume delete the first one').onClick(() => {
473        this.message.delete(0)
474      })
475    }
476    .width('100%')
477  }
478}
479
480
481@Entry
482@Component
483struct SetSample {
484  @Provide message: Set<number> = new Set([0, 1, 2, 3, 4])
485
486  build() {
487    Row() {
488      Column() {
489        Button('Provide init set').onClick(() => {
490          this.message = new Set([0, 1, 2, 3, 4, 5])
491        })
492        Child()
493      }
494      .width('100%')
495    }
496    .height('100%')
497  }
498}
499```
500
501### Provide_and_Consume支持联合类型实例
502
503@Provide和@Consume支持联合类型和undefined和null,在下面的示例中,count类型为string | undefined,点击父组件Parent中的Button改变count的属性或者类型,Child中也会对应刷新。
504
505```ts
506@Component
507struct Child {
508  // @Consume装饰的变量通过相同的属性名绑定其祖先组件Ancestors内的@Provide装饰的变量
509  @Consume count: string | undefined;
510
511  build() {
512    Column() {
513      Text(`count(${this.count})`)
514      Button(`count(${this.count}), Child`)
515        .onClick(() => this.count = 'Ancestors')
516    }
517    .width('50%')
518  }
519}
520
521@Component
522struct Parent {
523  build() {
524    Row({ space: 5 }) {
525      Child()
526    }
527  }
528}
529
530@Entry
531@Component
532struct Ancestors {
533  // @Provide装饰的联合类型count由入口组件Ancestors提供其后代组件
534  @Provide count: string | undefined = 'Child';
535
536  build() {
537    Column() {
538      Button(`count(${this.count}), Child`)
539        .onClick(() => this.count = undefined)
540      Parent()
541    }
542  }
543}
544```
545
546### \@Provide支持allowOverride参数
547
548allowOverride:\@Provide重写选项。
549
550> **说明:**
551>
552> 从API version 11开始使用。
553
554| 名称   | 类型   | 必填 | 说明                                                         |
555| ------ | ------ | ---- | ------------------------------------------------------------ |
556| allowOverride | string | 否 | 是否允许@Provide重写。允许在同一组件树下通过allowOverride重写同名的@Provide。如果开发者未写allowOverride,定义同名的@Provide,运行时会报错。 |
557
558```ts
559@Component
560struct MyComponent {
561  @Provide({allowOverride : "reviewVotes"}) reviewVotes: number = 10;
562}
563```
564
565完整示例如下:
566
567```ts
568@Component
569struct GrandSon {
570  // @Consume装饰的变量通过相同的属性名绑定其祖先内的@Provide装饰的变量
571  @Consume("reviewVotes") reviewVotes: number;
572
573  build() {
574    Column() {
575      Text(`reviewVotes(${this.reviewVotes})`) // Text显示10
576      Button(`reviewVotes(${this.reviewVotes}), give +1`)
577        .onClick(() => this.reviewVotes += 1)
578    }
579    .width('50%')
580  }
581}
582
583@Component
584struct Child {
585  @Provide({ allowOverride: "reviewVotes" }) reviewVotes: number = 10;
586
587  build() {
588    Row({ space: 5 }) {
589      GrandSon()
590    }
591  }
592}
593
594@Component
595struct Parent {
596  @Provide({ allowOverride: "reviewVotes" }) reviewVotes: number = 20;
597
598  build() {
599    Child()
600  }
601}
602
603@Entry
604@Component
605struct GrandParent {
606  @Provide("reviewVotes") reviewVotes: number = 40;
607
608  build() {
609    Column() {
610      Button(`reviewVotes(${this.reviewVotes}), give +1`)
611        .onClick(() => this.reviewVotes += 1)
612      Parent()
613    }
614  }
615}
616```
617
618在上面的示例中:
619- GrandParent声明了@Provide("reviewVotes") reviewVotes: number = 40
620- Parent是GrandParent的子组件,声明@Provide为allowOverride,重写父组件GrandParent的@Provide("reviewVotes") reviewVotes: number = 40。如果不设置allowOverride,则会抛出运行时报错,提示@Provide重复定义。Child同理。
621- GrandSon在初始化@Consume的时候,@Consume装饰的变量通过相同的属性名绑定其最近的祖先的@Provide装饰的变量。
622- GrandSon查找到相同属性名的@Provide在祖先Child中,所以@Consume("reviewVotes") reviewVotes: number初始化数值为10。如果Child中没有定义与@Consume同名的@Provide,则继续向上寻找Parent中的同名@Provide值为20,以此类推。
623- 如果查找到根节点还没有找到key对应的@Provide,则会报初始化@Consume找不到@Provide的报错。
624
625
626## 常见问题
627
628### \@BuilderParam尾随闭包情况下\@Provide未定义错误
629
630在此场景下,CustomWidget执行this.builder()创建子组件CustomWidgetChild时,this指向的是HomePage。因此找不到CustomWidget的\@Provide变量,所以下面示例会报找不到\@Provide错误,和\@BuilderParam连用的时候要谨慎this的指向。
631
632错误示例:
633
634```ts
635class Tmp {
636  a: string = ''
637}
638
639@Entry
640@Component
641struct HomePage {
642  @Builder
643  builder2($$: Tmp) {
644    Text(`${$$.a}测试`)
645  }
646
647  build() {
648    Column() {
649      CustomWidget() {
650        CustomWidgetChild({ builder: this.builder2 })
651      }
652    }
653  }
654}
655
656@Component
657struct CustomWidget {
658  @Provide('a') a: string = 'abc';
659  @BuilderParam
660  builder: () => void;
661
662  build() {
663    Column() {
664      Button('你好').onClick(() => {
665        if (this.a == 'ddd') {
666          this.a = 'abc';
667        }
668        else {
669          this.a = 'ddd';
670        }
671
672      })
673      this.builder()
674    }
675  }
676}
677
678@Component
679struct CustomWidgetChild {
680  @Consume('a') a: string;
681  @BuilderParam
682  builder: ($$: Tmp) => void;
683
684  build() {
685    Column() {
686      this.builder({ a: this.a })
687    }
688  }
689}
690```
691
692正确示例:
693
694```ts
695class Tmp {
696  name: string = ''
697}
698
699@Entry
700@Component
701struct HomePage {
702  @Provide('name') name: string = 'abc';
703
704  @Builder
705  builder2($$: Tmp) {
706    Text(`${$$.name}测试`)
707  }
708
709  build() {
710    Column() {
711      Button('你好').onClick(() => {
712        if (this.name == 'ddd') {
713          this.name = 'abc';
714        } else {
715          this.name = 'ddd';
716        }
717      })
718      CustomWidget() {
719        CustomWidgetChild({ builder: this.builder2 })
720      }
721    }
722  }
723}
724
725@Component
726struct CustomWidget {
727  @BuilderParam
728  builder: () => void;
729
730  build() {
731    this.builder()
732  }
733}
734
735@Component
736struct CustomWidgetChild {
737  @Consume('name') name: string;
738  @BuilderParam
739  builder: ($$: Tmp) => void;
740
741  build() {
742    Column() {
743      this.builder({ name: this.name })
744    }
745  }
746}
747```
748
749### 使用a.b(this.object)形式调用,不会触发UI刷新
750
751在build方法内,当@Provide与@Consume装饰的变量是Object类型、且通过a.b(this.object)形式调用时,b方法内传入的是this.object的原生对象,修改其属性,无法触发UI刷新。如下例中,通过静态方法或者使用this调用组件内部方法,修改组件中的this.dog.agethis.dog.name时,UI不会刷新。
752
753【反例】
754
755```ts
756class Animal {
757  name:string;
758  type:string;
759  age: number;
760
761  constructor(name:string, type:string, age:number) {
762    this.name = name;
763    this.type = type;
764    this.age = age;
765  }
766
767  static changeName(animal:Animal) {
768    animal.name = 'Jack';
769  }
770  static changeAge(animal:Animal) {
771    animal.age += 1;
772  }
773}
774
775@Entry
776@Component
777struct Zoo {
778  @Provide dog:Animal = new Animal('WangCai', 'dog', 2);
779
780  changeZooDogAge(animal:Animal) {
781    animal.age += 2;
782  }
783
784  build() {
785    Column({ space:10 }) {
786      Text(`Zoo: This is a ${this.dog.age}-year-old ${this.dog.type} named ${this.dog.name}.`)
787        .fontColor(Color.Red)
788        .fontSize(30)
789      Button('changeAge')
790        .onClick(()=>{
791          // 通过静态方法调用,无法触发UI刷新
792          Animal.changeAge(this.dog);
793        })
794      Button('changeZooDogAge')
795        .onClick(()=>{
796          // 使用this通过自定义组件内部方法调用,无法触发UI刷新
797          this.changeZooDogAge(this.dog);
798        })
799      ZooChild()
800    }
801  }
802}
803
804@Component
805struct ZooChild {
806
807  build() {
808    Column({ space:10 }) {
809      Text(`ZooChild`)
810        .fontColor(Color.Blue)
811        .fontSize(30)
812      ZooGrandChild()
813    }
814  }
815}
816
817@Component
818struct ZooGrandChild {
819  @Consume dog:Animal;
820
821  changeZooGrandChildName(animal:Animal) {
822    animal.name = 'Marry';
823  }
824
825  build() {
826    Column({ space:10 }) {
827      Text(`ZooGrandChild: This is a ${this.dog.age}-year-old ${this.dog.type} named ${this.dog.name}.`)
828        .fontColor(Color.Yellow)
829        .fontSize(30)
830      Button('changeName')
831        .onClick(()=>{
832          // 通过静态方法调用,无法触发UI刷新
833          Animal.changeName(this.dog);
834        })
835      Button('changeZooGrandChildName')
836        .onClick(()=>{
837          // 使用this通过自定义组件内部方法调用,无法触发UI刷新
838          this.changeZooGrandChildName(this.dog);
839        })
840    }
841  }
842}
843```
844
845可以通过如下先赋值、再调用新赋值的变量的方式为this.dog加上Proxy代理,实现UI刷新。
846
847【正例】
848
849```ts
850class Animal {
851  name:string;
852  type:string;
853  age: number;
854
855  constructor(name:string, type:string, age:number) {
856    this.name = name;
857    this.type = type;
858    this.age = age;
859  }
860
861  static changeName(animal:Animal) {
862    animal.name = 'Jack';
863  }
864  static changeAge(animal:Animal) {
865    animal.age += 1;
866  }
867}
868
869@Entry
870@Component
871struct Zoo {
872  @Provide dog:Animal = new Animal('WangCai', 'dog', 2);
873
874  changeZooDogAge(animal:Animal) {
875    animal.age += 2;
876  }
877
878  build() {
879    Column({ space:10 }) {
880      Text(`Zoo: This is a ${this.dog.age}-year-old ${this.dog.type} named ${this.dog.name}.`)
881        .fontColor(Color.Red)
882        .fontSize(30)
883      Button('changeAge')
884        .onClick(()=>{
885          // 通过赋值添加 Proxy 代理
886          let newDog = this.dog;
887          Animal.changeAge(newDog);
888        })
889      Button('changeZooDogAge')
890        .onClick(()=>{
891          // 通过赋值添加 Proxy 代理
892          let newDog = this.dog;
893          this.changeZooDogAge(newDog);
894        })
895      ZooChild()
896    }
897  }
898}
899
900@Component
901struct ZooChild {
902
903  build() {
904    Column({ space:10 }) {
905      Text(`ZooChild.`)
906        .fontColor(Color.Blue)
907        .fontSize(30)
908      ZooGrandChild()
909    }
910  }
911}
912
913@Component
914struct ZooGrandChild {
915  @Consume dog:Animal;
916
917  changeZooGrandChildName(animal:Animal) {
918    animal.name = 'Marry';
919  }
920
921  build() {
922    Column({ space:10 }) {
923      Text(`ZooGrandChild: This is a ${this.dog.age}-year-old ${this.dog.type} named ${this.dog.name}.`)
924        .fontColor(Color.Yellow)
925        .fontSize(30)
926      Button('changeName')
927        .onClick(()=>{
928          // 通过赋值添加 Proxy 代理
929          let newDog = this.dog;
930          Animal.changeName(newDog);
931        })
932      Button('changeZooGrandChildName')
933        .onClick(()=>{
934          // 通过赋值添加 Proxy 代理
935          let newDog = this.dog;
936          this.changeZooGrandChildName(newDog);
937        })
938    }
939  }
940}
941```
942