• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# \@Provide装饰器和\@Consume装饰器:与后代组件双向同步
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @liwenzhen3-->
5<!--Designer: @s10021109-->
6<!--Tester: @TerryTsao-->
7<!--Adviser: @zhang_yixin13-->
8
9\@Provide和\@Consume,应用于与后代组件的双向数据同步、状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,\@Provide和\@Consume摆脱参数传递机制的束缚,实现跨层级传递。
10
11其中\@Provide装饰的变量是在祖先组件中,可以理解为被“提供”给后代的状态变量。\@Consume装饰的变量是在后代组件中,去“消费(绑定)”祖先组件提供的变量。
12
13\@Provide/\@Consume是跨组件层级的双向同步。在阅读\@Provide和\@Consume文档前,建议开发者对UI范式基本语法和自定义组件有基本的了解。建议提前阅读:[基本语法概述](./arkts-basic-syntax-overview.md),[声明式UI描述](./arkts-declarative-ui-description.md),[自定义组件-创建自定义组件](./arkts-create-custom-components.md)。最佳实践请参考[状态管理最佳实践](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-status-management)14
15> **说明:**
16>
17> 从API version 9开始,这两个装饰器支持在ArkTS卡片中使用。
18>
19> 从API version 11开始,这两个装饰器支持在原子化服务中使用。
20>
21>API version 19及以前,\@Provide和\@Consume双向同步仅支持声明式节点场景。
22>
23> 从API version 20开始,@Consume装饰的变量支持设置默认值。当查找不到@Provide的匹配结果时,@Consume装饰的变量会使用默认值进行初始化;当查找到@Provide的匹配结果时,@Consume装饰的变量会优先使用@Provide匹配结果的值,默认值不生效。
24>
25> 从API version 20开始,通过配置[BuilderNode](../../reference/apis-arkui/js-apis-arkui-builderNode.md)的[BuildOptions](../../reference/apis-arkui/js-apis-arkui-builderNode.md#buildoptions12)参数`enableProvideConsumeCrossing`为true,使得\@Provide和\@Consume支持跨[BuilderNode](../../reference/apis-arkui/js-apis-arkui-builderNode.md)双向同步。但需要注意,BuilderNode会在上树前构造节点,所以BuilderNode内部定义的\@Consume需要设置默认值,并在BuilderNode上树后,重新获取最近的\@Provide数据,与之建立双向同步关系。具体可见[\@Consume在跨BuilderNode场景下和\@Provide建立双向同步](#consume在跨buildernode场景下和provide建立双向同步)。
26
27## 概述
28
29\@Provide/\@Consume装饰的状态变量有以下特性:
30
31- \@Provide装饰的状态变量自动对其所有后代组件可用,开发者不需要多次在组件之间传递变量。
32
33- 后代通过使用\@Consume去获取\@Provide提供的变量,建立在\@Provide和\@Consume之间的双向数据同步,与\@State/\@Link不同的是,前者可以更便捷的在多层级父子组件之间传递。
34
35- \@Provide和\@Consume通过变量名或者变量别名绑定,需要类型相同,否则会发生类型隐式转换,从而导致应用行为异常。
36
37```ts
38// 通过相同的变量名绑定
39@Provide age: number = 0;
40@Consume age: number;
41
42// 通过相同的变量别名绑定
43@Provide('a') id: number = 0;
44@Consume('a') age: number;
45
46// 通过Provide的变量别名和Consume的变量名相同绑定
47@Provide('a') id: number = 0;
48@Consume a: number;
49
50// 通过Provide的变量名和Consume的变量别名绑定
51@Provide id: number = 0;
52@Consume('id') a: number;
53
54```
55当\@Provide指定变量别名时,会同时保存变量名与变量别名,\@Consume在查找时,会优先以变量别名作为查找值去匹配,如果没有别名则用变量名作为查找值,只要\@Consume提供的查找值与\@Provide保存的变量名或别名中任意一项一致,即可成功建立绑定关系。
56
57## 装饰器说明
58
59\@State的规则同样适用于\@Provide,差异为\@Provide还作为多层后代的同步源。
60
61| \@Provide变量装饰器 | 说明                                       |
62| -------------- | ---------------------------------------- |
63| 装饰器参数          | 别名:常量字符串,可选。|
64| 同步类型           | 双向同步。<br/>从\@Provide变量到所有\@Consume变量以及相反的方向的数据同步。双向同步的操作与\@State和\@Link的组合相同。 |
65| 允许装饰的变量类型      | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>API version 10开始支持[Date类型](#装饰date类型变量)。<br/>API version 11及以上支持[Map](#装饰map类型变量)、[Set](#装饰set类型变量)类型、undefined和null类型、ArkUI框架定义的联合类型[Length](../../reference/apis-arkui/arkui-ts/ts-types.md#length)、[ResourceStr](../../reference/apis-arkui/arkui-ts/ts-types.md#resourcestr)、[ResourceColor](../../reference/apis-arkui/arkui-ts/ts-types.md#resourcecolor)类型以及这些类型的联合类型,示例见[@Provide和Consume支持联合类型实例](#provide和consume支持联合类型实例)。<br/>支持类型的场景请参考[观察变化](#观察变化)。|
66| 不允许装饰的变量类型 | 不支持装饰Function类型。      |
67| 被装饰变量的初始值      | 必须本地初始化。                                    |
68| 支持allowOverride参数          | 允许重写,只要声明了allowOverride,则别名和属性名都可以被Override。示例见[\@Provide支持allowOverride参数](#provide支持allowoverride参数)。 |
69
70| \@Consume变量装饰器 | 说明                                       |
71| -------------- | ---------------------------------------- |
72| 装饰器参数          | 别名:常量字符串,可选。 |
73| 同步类型           | 双向同步:从\@Provide变量(具体请参见\@Provide)到所有\@Consume变量,以及相反的方向。双向同步操作与\@State和\@Link的组合相同。 |
74| 允许装饰的变量类型      | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>API version 10开始支持[Date类型](#装饰date类型变量)。<br/>API version 11及以上支持[Map](#装饰map类型变量)、[Set](#装饰set类型变量)类型、undefined和null类型、ArkUI框架定义的联合类型[Length](../../reference/apis-arkui/arkui-ts/ts-types.md#length)、[ResourceStr](../../reference/apis-arkui/arkui-ts/ts-types.md#resourcestr)、[ResourceColor](../../reference/apis-arkui/arkui-ts/ts-types.md#resourcecolor)类型以及这些类型的联合类型,示例见[@Provide和Consume支持联合类型实例](#provide和consume支持联合类型实例)。<br/>支持类型的场景请参考[观察变化](#观察变化)。 <br/>**说明:** <br/>API version 20之前,\@Consume装饰的变量,在其父组件或者祖先组件上,必须有对应的属性和别名的\@Provide装饰的变量。
75| 被装饰变量的初始值      | 从API version 20开始,\@Consume支持设置默认值。若存在匹配成功的\@Provide,则会使用\@Provide的变量值作为初始值。示例见[\@Consume装饰的变量支持设置默认值](#consume装饰的变量支持设置默认值)。                            |
76
77## 变量的传递/访问规则说明
78
79| \@Provide传递/访问 | 说明                                       |
80| -------------- | ---------------------------------------- |
81| 从父组件初始化和更新     | 可选,允许父组件中常规变量(常规变量对@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。 |
82| 用于初始化子组件       | 允许,可用于初始化\@State、\@Link、\@Prop、\@Provide。 |
83| 和父组件同步         | 否。                                       |
84| 和后代组件同步        | 和\@Consume双向同步。                          |
85| 是否支持组件外访问      | 私有,仅可以在所属组件内访问。                          |
86
87  **图1** \@Provide初始化规则图示
88
89![zh-cn_image_0000001552614217](figures/zh-cn_image_0000001552614217.png)
90
91| \@Consume传递/访问 | 说明                                       |
92| -------------- | ---------------------------------------- |
93| 从父组件初始化和更新     | 禁止。      |
94| 用于初始化子组件       | 允许,可用于初始化\@State、\@Link、\@Prop、\@Provide。 |
95| 和祖先组件同步        | 和\@Provide双向同步。                          |
96| 是否支持组件外访问      | 私有,仅可以在所属组件内访问                           |
97
98  **图2** \@Consume初始化规则图示
99
100
101![zh-cn_image_0000001502094666](figures/zh-cn_image_0000001502094666.png)
102
103## 观察变化和行为表现
104
105### 观察变化
106
107- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
108
109- 当装饰的数据类型为class或者Object的时候,可以观察到赋值和属性赋值的变化(属性为Object.keys(observedObject)返回的所有属性)。
110
111- 当装饰Array时,可以观察到数组本身、数组项的赋值及其API操作带来的变化。
112
113- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性,详见[装饰Date类型变量](#装饰date类型变量)。
114
115- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口`set`, `clear`, `delete` 更新Map的值。详见[装饰Map类型变量](#装饰map类型变量)。
116
117- 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口`add`, `clear`, `delete` 更新Set的值。详见[装饰Set类型变量](#装饰set类型变量)。
118
119### 框架行为
120
1211. 初始渲染:
122   1. \@Provide装饰的变量会以Map的形式,传递给当前\@Provide所属组件的所有子组件。
123   2. 子组件中如果使用\@Consume变量,则会在Map中查找是否有该变量名/alias(别名)对应的\@Provide的变量。在API version 20之前,如果查找不到,框架会抛出JS ERROR。从API version 20开始,如果查找不到,会判断\@Consume装饰的变量是否设置了默认值,如果没有设置默认值,框架会抛出JS ERROR。
124   3. 在初始化\@Consume变量时,如果在Map中有该变量名/alias(别名)对应的\@Provide的变量,则和\@State/\@Link的流程类似,\@Consume变量会在Map中查找到对应的\@Provide变量进行保存,并把自己注册给\@Provide。
125   4. 从API version 20开始,在初始化\@Consume变量时,如果在Map中没有该变量名/alias(别名)对应的\@Provide的变量,而\@Consume的变量设置了默认值时,\@Consume变量会利用默认值创建一个临时的数据源,保证通知链路的连续性。
126
1272. 当\@Provide装饰的数据变化时:
128   1. 通过初始渲染的步骤可知,子组件\@Consume已把自己注册给父组件。父组件\@Provide变量变更后,会遍历更新所有依赖它的系统组件(elementid)和状态变量(\@Consume)。
129   2. 通知\@Consume更新后,子组件所有依赖\@Consume的系统组件(elementId)都会被通知更新。以此实现\@Provide对\@Consume状态数据同步。
130
1313. 当\@Consume装饰的数据变化时:
132
133   通过初始渲染的步骤可知,子组件\@Consume持有\@Provide的实例。在\@Consume更新后调用\@Provide的更新方法,将更新的数值同步回\@Provide,以此实现\@Consume向\@Provide的同步更新。
134
135![Provide_Consume_framework_behavior_withDefault](figures/Provide_Consume_framework_behavior_withDefault.png)
136
137## 限制条件
138
1391. \@Provide/\@Consume的参数key必须为string类型,否则编译期会报错。
140
141    ```ts
142    // 错误写法,编译报错
143    let change: number = 10;
144    @Provide(change) message: string = 'Hello';
145
146    // 正确写法
147    let change: string = 'change';
148    @Provide(change) message: string = 'Hello';
149    ```
150
1512. \@Consume装饰的变量不能在构造参数中传入初始化,否则编译期会报错。\@Consume仅能通过key来匹配对应的\@Provide变量或者从API version 20开始设置默认值进行初始化。
152
153    【反例】
154
155    ```ts
156    @Component
157    struct Child {
158      @Consume msg: string;
159
160      build() {
161        Text(this.msg)
162      }
163    }
164
165    @Entry
166    @Component
167    struct Parent {
168      @Provide message: string = 'Hello';
169
170      build() {
171        Column() {
172          // 错误写法,不允许外部传入初始化
173          Child({msg: 'Hello'})
174        }
175      }
176    }
177    ```
178
179    【正例】
180
181    ```ts
182    @Component
183    struct Child {
184      @Consume num: number;
185      // 从API version 20开始,@Consume装饰的变量支持设置默认值
186      @Consume num1: number = 17;
187
188      build() {
189        Column() {
190          Text(`num的值: ${this.num}`)
191          Text(`num1的值:${this.num1}`)
192        }
193      }
194    }
195
196    @Entry
197    @Component
198    struct Parent {
199      @Provide num: number = 10;
200
201      build() {
202        Column() {
203          Text(`num的值: ${this.num}`)
204          Child()
205        }
206      }
207    }
208    ```
209
2103. \@Provide的key重复定义时,框架会抛出运行时错误,提醒开发者重复定义key,如果开发者需要重复key,可以使用[allowoverride](#provide支持allowoverride参数)。
211
212    ```ts
213    // 错误写法,a重复定义
214    @Provide('a') count: number = 10;
215    @Provide('a') num: number = 10;
216
217    // 正确写法
218    @Provide('a') count: number = 10;
219    @Provide('b') num: number = 10;
220    ```
221
2224. 在API version 20之前,初始化\@Consume变量时,如果开发者没有定义对应key的\@Provide变量,框架会抛出运行时错误,提示开发者初始化\@Consume变量失败,原因是无法找到其对应key的\@Provide变量。从API version 20开始,初始化\@Consume变量时,如果开发者没有定义对应key的\@Provide变量,同时没有设置默认值,框架会抛出运行时错误,提示开发者初始化\@Consume变量失败,原因是无法找到其对应key的\@Provide变量同时也没有设置默认值。
223
224    【反例】
225
226    ```ts
227    @Component
228    struct Child {
229      @Consume num: number;
230
231      build() {
232        Column() {
233          Text(`num的值: ${this.num}`)
234        }
235      }
236    }
237
238    @Entry
239    @Component
240    struct Parent {
241      // 错误写法,缺少@Provide
242      num: number = 10;
243
244      build() {
245        Column() {
246          Text(`num的值: ${this.num}`)
247          Child()
248        }
249      }
250    }
251    ```
252
253    【正例】
254
255    ```ts
256    @Component
257    struct Child {
258      @Consume num: number;
259      // 正确写法 从API version 20开始,@Consume装饰的变量支持设置默认值
260      @Consume num_with_defaultValue: number = 6;
261
262      build() {
263        Column() {
264          Text(`num的值: ${this.num}`)
265          Text(`num_with_defaultValue的值:${this.num_with_defaultValue}`)
266        }
267      }
268    }
269
270    @Entry
271    @Component
272    struct Parent {
273      // 正确写法
274      @Provide num: number = 10;
275
276      build() {
277        Column() {
278          Text(`num的值: ${this.num}`)
279          Child()
280        }
281      }
282    }
283    ```
284
2855. \@Provide与\@Consume不支持装饰Function类型的变量,框架会抛出运行时错误。
286
2876. 从API version 20开始,支持跨BuilderNode配对\@Provide/\@Consume。在BuilderNode上树时,\@Consume通过key匹配找到最近的\@Provide,两者类型需要一致,如果不一致,则会抛出运行时错误。
288需要注意类型不相等判断,包括类实例的判断,比如:
289```ts
290class A {}
291class B {}
292// 两个message都为object类型,但其构造函数不同,属于不同类型
293@Provide message: A = new A();
294@Consume message: B = new B();
295```
296在非BuilderNode场景中,仍建议配对的\@Provide/\@Consume类型一致。虽然在运行时不会有强校验,但在\@Consume装饰的变量初始化时,会隐式转换成\@Provide装饰变量的类型。
297```ts
298import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI';
299
300@Builder
301function buildText() {
302  Column() {
303    Child()
304  }
305}
306
307class TextNodeController extends NodeController {
308  private builderNode: BuilderNode<[]> | null = null;
309
310  constructor() {
311    super();
312  }
313
314  makeNode(context: UIContext): FrameNode | null {
315    this.builderNode = new BuilderNode(context);
316    // 配置跨BuilderNode支持@Provide/@Consume
317    this.builderNode.build(wrapBuilder(buildText), undefined,
318      { enableProvideConsumeCrossing: true });
319    // 将BuilderNode的根节点挂载到NodeContainer
320    return this.builderNode.getFrameNode();
321  }
322}
323
324@Entry
325@Component
326struct Index {
327  @Provide message: string = 'hello';
328  controller: TextNodeController = new TextNodeController();
329
330  build() {
331    Column() {
332      NodeContainer(this.controller)
333        .width('100%')
334        .height(100)
335    }
336    .width('100%')
337    .height('100%')
338  }
339}
340
341
342@Component
343struct Child {
344  // Child通过BuilderNode上树后,@Consume和Index中的@Provide建立连接时发现类型不一致,抛出运行时错误
345  @Consume message: number = 0;
346
347  build() {
348    Column() {
349      Text(`@Consume ${this.message}`)
350    }
351  }
352}
353```
354
355## 使用场景
356
357以下示例是@Provide变量与后代组件中@Consume变量进行双向同步的场景。当分别点击ToDo和ToDoItem组件内的Button时,count的更改会双向同步在ToDo和ToDoItem中。
358
359```ts
360@Component
361struct ToDoItem {
362  // @Consume装饰的变量通过相同的属性名绑定其祖先组件ToDo内的@Provide装饰的变量
363  @Consume count: number;
364
365  build() {
366    Column() {
367      Text(`count(${this.count})`)
368      Button(`count(${this.count}), count + 1`)
369        .onClick(() => this.count += 1)
370    }
371    .width('50%')
372  }
373}
374
375@Component
376struct ToDoList {
377  build() {
378    Row({ space: 5 }) {
379      ToDoItem()
380      ToDoItem()
381    }
382  }
383}
384
385@Component
386struct ToDoDemo {
387  build() {
388    ToDoList()
389  }
390}
391
392@Entry
393@Component
394struct ToDo {
395  // @Provide装饰的变量count由入口组件ToDo提供其后代组件
396  @Provide count: number = 0;
397
398  build() {
399    Column() {
400      Button(`count(${this.count}), count + 1`)
401        .onClick(() => this.count += 1)
402      ToDoDemo()
403    }
404  }
405}
406```
407### 装饰Map类型变量
408
409> **说明:**
410>
411> 从API version 11开始,\@Provide,\@Consume支持Map类型。
412
413以下示例中,message类型为Map\<number, string\>,点击Button改变message的值,视图会随之刷新。
414
415```ts
416@Component
417struct Child {
418  @Consume message: Map<number, string>
419
420  build() {
421    Column() {
422      ForEach(Array.from(this.message.entries()), (item: [number, string]) => {
423        Text(`${item[0]}`).fontSize(30)
424        Text(`${item[1]}`).fontSize(30)
425        Divider()
426      })
427      Button('Consume init Map').onClick(() => {
428        this.message = new Map([[0, 'a'], [1, 'b'], [3, 'c']])
429      })
430      Button('Consume set new one').onClick(() => {
431        this.message.set(4, 'd')
432      })
433      Button('Consume clear').onClick(() => {
434        this.message.clear()
435      })
436      Button('Consume replace the first item').onClick(() => {
437        this.message.set(0, 'aa')
438      })
439      Button('Consume delete the first item').onClick(() => {
440        this.message.delete(0)
441      })
442    }
443  }
444}
445
446
447@Entry
448@Component
449struct MapSample {
450  @Provide message: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']])
451
452  build() {
453    Row() {
454      Column() {
455        Button('Provide init Map').onClick(() => {
456          this.message = new Map([[0, 'a'], [1, 'b'], [3, 'c'], [4, 'd']])
457        })
458        Child()
459      }
460      .width('100%')
461    }
462    .height('100%')
463  }
464}
465```
466
467### 装饰Set类型变量
468
469> **说明:**
470>
471> 从API version 11开始,\@Provide,\@Consume支持Set类型。
472
473以下示例中,message类型为Set\<number\>,点击Button改变message的值,视图会随之刷新。
474
475```ts
476@Component
477struct Child {
478  @Consume message: Set<number>
479
480  build() {
481    Column() {
482      ForEach(Array.from(this.message.entries()), (item: [number, number]) => {
483        Text(`${item[0]}`).fontSize(30)
484        Divider()
485      })
486      Button('Consume init set').onClick(() => {
487        this.message = new Set([0, 1, 2, 3, 4])
488      })
489      Button('Consume set new one').onClick(() => {
490        this.message.add(5)
491      })
492      Button('Consume clear').onClick(() => {
493        this.message.clear()
494      })
495      Button('Consume delete the first one').onClick(() => {
496        this.message.delete(0)
497      })
498    }
499    .width('100%')
500  }
501}
502
503
504@Entry
505@Component
506struct SetSample {
507  @Provide message: Set<number> = new Set([0, 1, 2, 3, 4])
508
509  build() {
510    Row() {
511      Column() {
512        Button('Provide init set').onClick(() => {
513          this.message = new Set([0, 1, 2, 3, 4, 5])
514        })
515        Child()
516      }
517      .width('100%')
518    }
519    .height('100%')
520  }
521}
522```
523
524### 装饰Date类型变量
525
526以下示例中,selectedDate类型为Date,点击Button改变selectedDate的值,视图会随之刷新。
527
528```ts
529@Component
530struct Child {
531  @Consume selectedDate: Date;
532
533  build() {
534    Column() {
535      Button(`child increase the day by 1`)
536        .onClick(() => {
537          this.selectedDate.setDate(this.selectedDate.getDate() + 1)
538        })
539      Button('child update the new date')
540        .margin(10)
541        .onClick(() => {
542          this.selectedDate = new Date('2023-09-09')
543        })
544      DatePicker({
545        start: new Date('1970-1-1'),
546        end: new Date('2100-1-1'),
547        selected: this.selectedDate
548      })
549    }
550  }
551}
552
553@Entry
554@Component
555struct Parent {
556  @Provide selectedDate: Date = new Date('2021-08-08')
557
558  build() {
559    Column() {
560      Button('parent increase the day by 1')
561        .margin(10)
562        .onClick(() => {
563          this.selectedDate.setDate(this.selectedDate.getDate() + 1)
564        })
565      Button('parent update the new date')
566        .margin(10)
567        .onClick(() => {
568          this.selectedDate = new Date('2023-07-07')
569        })
570      DatePicker({
571        start: new Date('1970-1-1'),
572        end: new Date('2100-1-1'),
573        selected: this.selectedDate
574      })
575      Child()
576    }
577  }
578}
579```
580
581### Provide和Consume支持联合类型实例
582
583@Provide和@Consume支持联合类型和undefined和null。以下示例中,count类型为string | undefined,当点击父组件Parent中的Button改变count的属性或者类型时,Child中也会对应刷新。
584
585```ts
586@Component
587struct Child {
588  // @Consume装饰的变量通过相同的属性名绑定其祖先组件Ancestors内的@Provide装饰的变量
589  @Consume count: string | undefined;
590
591  build() {
592    Column() {
593      Text(`count(${this.count})`)
594      Button(`count(${this.count}), Child`)
595        .onClick(() => this.count = 'Ancestors')
596    }
597    .width('50%')
598  }
599}
600
601@Component
602struct Parent {
603  build() {
604    Row({ space: 5 }) {
605      Child()
606    }
607  }
608}
609
610@Entry
611@Component
612struct Ancestors {
613  // @Provide装饰的联合类型count由入口组件Ancestors提供其后代组件
614  @Provide count: string | undefined = 'Child';
615
616  build() {
617    Column() {
618      Button(`count(${this.count}), Child`)
619        .onClick(() => this.count = undefined)
620      Parent()
621    }
622  }
623}
624```
625
626### \@Provide支持allowOverride参数
627
628allowOverride:\@Provide重写选项。
629
630> **说明:**
631>
632> 从API version 11开始使用。
633
634| 名称   | 类型   | 必填 | 说明                                                         |
635| ------ | ------ | ---- | ------------------------------------------------------------ |
636| allowOverride | string | 否 | 是否允许@Provide重写。允许在同一组件树下通过allowOverride重写同名的@Provide。如果开发者未写allowOverride,定义同名的@Provide,运行时会报错。 |
637
638```ts
639@Component
640struct MyComponent {
641  @Provide({allowOverride : 'reviewVotes'}) reviewVotes: number = 10;
642}
643```
644
645完整示例如下:
646
647```ts
648@Component
649struct GrandSon {
650  // @Consume装饰的变量通过相同的属性名绑定其祖先内的@Provide装饰的变量
651  @Consume('reviewVotes') reviewVotes: number;
652
653  build() {
654    Column() {
655      Text(`reviewVotes(${this.reviewVotes})`) // Text显示10
656      Button(`reviewVotes(${this.reviewVotes}), give +1`)
657        .onClick(() => this.reviewVotes += 1)
658    }
659    .width('50%')
660  }
661}
662
663@Component
664struct Child {
665  @Provide({ allowOverride: 'reviewVotes' }) reviewVotes: number = 10;
666
667  build() {
668    Row({ space: 5 }) {
669      GrandSon()
670    }
671  }
672}
673
674@Component
675struct Parent {
676  @Provide({ allowOverride: 'reviewVotes' }) reviewVotes: number = 20;
677
678  build() {
679    Child()
680  }
681}
682
683@Entry
684@Component
685struct GrandParent {
686  @Provide('reviewVotes') reviewVotes: number = 40;
687
688  build() {
689    Column() {
690      Button(`reviewVotes(${this.reviewVotes}), give +1`)
691        .onClick(() => this.reviewVotes += 1)
692      Parent()
693    }
694  }
695}
696```
697
698在上面的示例中:
699- GrandParent声明了@Provide('reviewVotes') reviewVotes: number = 40。
700- Parent是GrandParent的子组件,声明@Provide为allowOverride,重写父组件GrandParent的@Provide('reviewVotes') reviewVotes: number = 40。如果不设置allowOverride,则会抛出运行时报错,提示@Provide重复定义。Child同理。
701- GrandSon在初始化@Consume的时候,@Consume装饰的变量通过相同的属性名绑定其最近的祖先的@Provide装饰的变量。
702- GrandSon查找到相同属性名的@Provide在祖先Child中,所以@Consume('reviewVotes') reviewVotes: number初始化数值为10。如果Child中没有定义与@Consume同名的@Provide,则继续向上寻找Parent中的同名@Provide值为20,以此类推。
703- 如果查找到根节点还没有找到key对应的@Provide,则会报初始化@Consume找不到@Provide的报错。
704
705### \@Consume装饰的变量支持设置默认值
706
707> **说明:**
708>
709> 从API version 20开始,\@Consume装饰的变量支持设置默认值。
710
711```ts
712@Component
713struct MyComponent {
714  @Consume('withDefault') defaultValue: number = 10;
715}
716```
717
718完整示例如下:
719
720```ts
721@Entry
722@Component
723struct Parent {
724  @Provide('firstKey') provideOne: string | undefined = undefined;
725  @Provide('secondKey') provideTwo: string = 'the second provider';
726
727  build(){
728    Column(){
729      Row(){
730        Column() {
731          Text(`${this.provideOne}`)
732          Text(`${this.provideTwo}`)
733        }
734
735        Column(){
736          // 点击change provideOne按钮,provideOne和子组件中的textOne属性会同时变化
737          Button('change provideOne')
738            .onClick(() => {
739              this.provideOne = undefined;
740            })
741          // 点击change provideTwo按钮,provideTwo和子组件中的textTwo属性会同时变化
742          Button('change provideTwo')
743            .onClick(() => {
744              this.provideTwo = 'the next provider';
745            })
746        }
747      }
748
749      Row(){
750        Column() {
751          Child()
752        }
753      }
754    }
755  }
756}
757
758@Component
759struct Child {
760  // @Consume装饰的变量通过相同的别名绑定其祖先内的@Provide装饰的变量,同时设置默认值
761  @Consume('firstKey') textOne: string | undefined = 'child';
762  // @Consume装饰的变量通过相同的别名绑定其祖先内的@Provide装饰的变量,没有设置默认值
763  @Consume('secondKey') textTwo: string;
764  // @Consume装饰的变量在祖先内没有匹配成功的@Provide装饰的变量,但设置了默认值
765  @Consume('thirdKey') textThree: string = 'defaultValue';
766
767  build(){
768    Column() {
769      Text(`${this.textOne}`)
770      Text(`${this.textTwo}`)
771      Text(`${this.textThree}`)
772      // 点击change textOne按钮,textOne和父组件的provideOne会同时变化
773      Button('change textOne')
774        .onClick(() => {
775          this.textOne = 'not undefined';
776        })
777      // 点击change textTwo按钮,textTwo和父组件的provideTwo会同时变化
778      Button('change textTwo')
779        .onClick(() => {
780          this.textTwo = 'change textTwo';
781        })
782    }
783  }
784}
785```
786
787在上面的示例中:
788- Parent声明了@Provide('firstKey') provideOne: string | undefined = undefined 与 @Provide('secondKey') provideTwo: string = 'the second provider'。
789- Child声明了@Consume('firstKey') textOne: string | undefined = 'child',@Consume('secondKey') textTwo: string 与 @Consume('thirdKey') textThree: string = 'defaultValue'。
790- Child是Parent的子组件,Child在初始化@Consume装饰的三个属性时,textOne根据'firstKey'别名绑定Parent中的provideOne属性,provideOne的值会覆盖textOne的默认值,所以textOne初始化的值为undefined;textTwo根据'secondKey'别名绑定Parent中的providedTwo属性,textTwo初始化的值为'the second provider';textThree在祖先组件中不存在匹配结果,如果@Consume没有设置默认值,则会抛出运行时错误,示例中textThree有默认值'defaultValue',所以textThree初始化的值为'defaultValue'。
791- @Consume装饰的属性设置的默认值仅在祖先组件没有匹配结果时才生效,有匹配结果时无影响。
792
793### \@Consume在跨BuilderNode场景下和\@Provide建立双向同步
794BuilderNode支持\@Provide/\@Consume,需注意:
7951. 在BuilderNode子树中定义的\@Consume需要设置默认值,或者在子树中已存在配对的\@Provide,否则会发生运行时报错。
7962. BuilderNode上树后,设置默认值的\@Consume会向上查找\@Provide,根据key的匹配规则找到最近的\@Provide后,会和\@Provide建立双向同步关系。如果找不到配对的\@Provide,则\@Consume仍使用默认值。
7973. 建立双向同步的关系后,如果\@Provide装饰变量的值和\@Consume的默认值不同,则会回调\@Consume的\@Watch方法,以及与\@Consume有同步关系的变量的\@Watch方法,例如\@Consume通知与其双向同步的\@Link触发\@Watch方法。
7984. BuilderNode下树后,\@Consume会再次试图查找对应的\@Provide,如果发现下树后无法再找到之前配对的\@Provide,则断开和\@Provide的双向同步关系,\@Consume装饰的变量恢复成默认值。
7995. \@Consume断开和\@Provide的连接,恢复成默认值时,会判断\@Consume装饰变量的值从和\@Provide变为\@Consume的默认值是否有变化,如果有变化,则会回调\@Consume以及与其有同步关系变量的\@Watch方法。
800
801在下面的例子中:
8021. 点击`add Child`:
803    - 构建BuilderNode下的子节点`Child`,`Child`中\@Consume未找到\@Provide,使用本地默认值`default value`初始化。
804    - BuilderNode上树时,`Child`中\@Consume向上找到最近的`Index`中的\@Provide,将\@Consume从默认值更新为\@Provide的值,并回调\@Consume的\@Watch方法。
8052. \@Provide和\@Consume配对后,建立双向同步关系。点击```Text(`@Provide: ${this.message}`)```和```Text(`@Consume ${this.message}`)```,\@Provide和\@Consume绑定的Text组件刷新,并回调\@Provide和\@Consume的\@Watch方法。
8063. 点击`remove Child`, BuilderNode子节点下树,`Child`中的\@Consume和`Index`中的\@Provide断开连接,`Child`中的\@Consume恢复成默认值,并回调\@Consume的\@Watch方法。
8074. 点击`dispose Child`,释放BuilderNode下子节点,BuilderNode子节点`Child`销毁,执行aboutToDisappear。
808
809```ts
810import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI';
811
812@Builder
813function buildText() {
814  Column() {
815    Child()
816  }
817}
818
819class TextNodeController extends NodeController {
820  private rootNode: FrameNode | null = null;
821  private uiContext: UIContext | null = null;
822  private builderNode: BuilderNode<[]> | null = null;
823
824  constructor() {
825    super();
826  }
827
828  makeNode(context: UIContext): FrameNode | null {
829    this.rootNode = new FrameNode(context);
830    this.uiContext = context;
831    // 将rootNode节点挂载在NodeContainer下
832    return this.rootNode;
833  }
834
835  addBuilderNode(): void {
836    if (this.builderNode === null && this.uiContext && this.rootNode) {
837      this.builderNode = new BuilderNode(this.uiContext);
838      // 配置跨BuilderNode支持@Provide/@Consume
839      this.builderNode.build(wrapBuilder(buildText), undefined,
840        { enableProvideConsumeCrossing: true });
841      // 将BuilderNode的根节点挂载到rootNode节点下
842      this.rootNode.appendChild(this.builderNode.getFrameNode());
843    }
844  }
845
846  removeBuilderNode(): void {
847    if (this.rootNode && this.builderNode) {
848      // 从rootNode节点下的BuildNode节点移除
849      this.rootNode.removeChild(this.builderNode.getFrameNode());
850    }
851  }
852
853  disposeNode(): void {
854    if (this.rootNode && this.builderNode) {
855      // 立即释放当前BuilderNode
856      this.builderNode.dispose();
857    }
858  }
859}
860
861@Entry
862@Component
863struct Index {
864  @Provide @Watch('onChange') message: string = 'hello';
865  controller: TextNodeController = new TextNodeController();
866
867  onChange() {
868    console.info(`Index Provide change ${this.message}`);
869  }
870
871  build() {
872    Column() {
873      Text(`@Provide: ${this.message}`)
874        .fontSize(20)
875        .onClick(() => {
876          this.message += ' Provide';
877        })
878
879      // 执行BuilderNode的build方法,构造Child自定义组件
880      // 并将BuilderNode挂载在NodeContainer下
881      // Child中@Consume可以和当前Index中的@Provide配对
882      // @Consume装饰的变量message从default value变为hello,并回调@Consume的@Watch方法
883      Button('add Child')
884        .onClick(() => {
885          this.controller.addBuilderNode();
886        })
887      // 将BuilderNode下的节点从NodeContainer上移除
888      // @Consume修饰的变量message从和@Provide配对的值变为default value,并回调@Consume的@Watch方法
889      Button('remove Child')
890        .onClick(() => {
891          this.controller.removeBuilderNode();
892        })
893
894      // 立即释放当前BuilderNode,BuilderNode下节点销毁,Child组件执行aboutToDisappear
895      Button('dispose Child')
896        .onClick(() => {
897          this.controller.disposeNode();
898        })
899      NodeContainer(this.controller)
900        .width('100%')
901        .height(100)
902        .backgroundColor(Color.Pink)
903    }
904    .width('100%')
905    .height('100%')
906  }
907}
908
909
910@Component
911struct Child {
912  @Consume @Watch('onChange') message: string = 'default value';
913
914  onChange() {
915    console.info(`Child Consume change ${this.message}`);
916  }
917
918  aboutToDisappear(): void {
919    console.info(`Child aboutToDisappear`);
920  }
921
922  build() {
923    Column() {
924      Text(`@Consume ${this.message}`)
925        .fontSize(20)
926        .onClick(() => {
927          this.message += ' Consume';
928        })
929    }
930  }
931}
932```
933
934## 常见问题
935
936### \@BuilderParam尾随闭包情况下\@Provide未定义错误
937
938在此[尾随闭包](arkts-builderparam.md#尾随闭包初始化组件)场景下,CustomWidget执行this.builder()创建子组件CustomWidgetChild时,this指向的是HomePage。因此找不到CustomWidget的\@Provide变量,所以下面示例会报找不到\@Provide错误,和\@BuilderParam连用的时候要谨慎this的指向。
939
940错误示例:
941
942```ts
943class Tmp {
944  a: string = ''
945}
946
947@Entry
948@Component
949struct HomePage {
950  // 错误点1:HomePage未声明@Provide
951  @Builder
952  builder2($$: Tmp) {
953    Text(`${$$.a}测试`)
954  }
955
956  build() {
957    Column() {
958      // 错误点2:使用尾随闭包的形式将创建CustomWidgetChild的函数传递给CustomWidget,此时尾随闭包中this指向HomePage
959      CustomWidget() {
960        CustomWidgetChild({ builder: this.builder2 })
961      }
962    }
963  }
964}
965
966@Component
967struct CustomWidget {
968  // 错误点3:@Provide变量声明在CustomWidget中,仅有CustomWidget自身及其子组件能够消费
969  @Provide('a') a: string = 'abc';
970  @BuilderParam
971  builder: () => void;
972
973  build() {
974    Column() {
975      Button('你好').onClick(() => {
976        if (this.a == 'ddd') {
977          this.a = 'abc';
978        }
979        else {
980          this.a = 'ddd';
981        }
982
983      })
984      this.builder()
985    }
986  }
987}
988
989@Component
990struct CustomWidgetChild {
991  // 错误点4:尝试消费CustomWidget的@Provide('a'),但实际上CustomWidgetChild的父组件为HomePage,无法找到对应的@Provide
992  @Consume('a') a: string;
993  @BuilderParam
994  builder: ($$: Tmp) => void;
995
996  build() {
997    Column() {
998      this.builder({ a: this.a })
999    }
1000  }
1001}
1002```
1003
1004正确示例:
1005
1006```ts
1007class Tmp {
1008  name: string = ''
1009}
1010
1011@Entry
1012@Component
1013struct HomePage {
1014  // 修正点1:将@Provide声明在Entry组件(根作用域),确保子组件能正确消费
1015  @Provide('name') name: string = 'abc';
1016
1017  @Builder
1018  builder2($$: Tmp) {
1019    Text(`${$$.name}测试`)
1020  }
1021
1022  build() {
1023    Column() {
1024      Button('你好').onClick(() => {
1025        if (this.name == 'ddd') {
1026          this.name = 'abc';
1027        } else {
1028          this.name = 'ddd';
1029        }
1030      })
1031      // 修正点2:CustomWidget不再声明@Provide,仅作为容器传递builder
1032      CustomWidget() {
1033        CustomWidgetChild({ builder: this.builder2 })
1034      }
1035    }
1036  }
1037}
1038
1039@Component
1040struct CustomWidget {
1041  @BuilderParam
1042  builder: () => void;
1043
1044  build() {
1045    this.builder()
1046  }
1047}
1048
1049@Component
1050struct CustomWidgetChild {
1051  // 修正点3:@Consume从根作用域(HomePage)获取@Provide('name'),作用域正确
1052  @Consume('name') name: string;
1053  @BuilderParam
1054  builder: ($$: Tmp) => void;
1055
1056  build() {
1057    Column() {
1058      this.builder({ name: this.name })
1059    }
1060  }
1061}
1062```
1063
1064### 使用a.b(this.object)形式调用,不会触发UI刷新
1065
1066在build方法内,当@Provide与@Consume装饰的变量是Object类型、且通过a.b(this.object)形式调用时,b方法内传入的是this.object的原始对象,修改其属性,无法触发UI刷新。如下例中,通过静态方法或者使用this调用组件内部方法,修改组件中的this.dog.agethis.dog.name时,UI不会刷新。
1067
1068【反例】
1069
1070```ts
1071class Animal {
1072  name:string;
1073  type:string;
1074  age: number;
1075
1076  constructor(name:string, type:string, age:number) {
1077    this.name = name;
1078    this.type = type;
1079    this.age = age;
1080  }
1081
1082  static changeName(animal:Animal) {
1083    animal.name = 'Jack';
1084  }
1085  static changeAge(animal:Animal) {
1086    animal.age += 1;
1087  }
1088}
1089
1090@Entry
1091@Component
1092struct Zoo {
1093  @Provide dog:Animal = new Animal('WangCai', 'dog', 2);
1094
1095  changeZooDogAge(animal:Animal) {
1096    animal.age += 2;
1097  }
1098
1099  build() {
1100    Column({ space:10 }) {
1101      Text(`Zoo: This is a ${this.dog.age}-year-old ${this.dog.type} named ${this.dog.name}.`)
1102        .fontColor(Color.Red)
1103        .fontSize(30)
1104      Button('changeAge')
1105        .onClick(()=>{
1106          // 通过静态方法调用,无法触发UI刷新
1107          Animal.changeAge(this.dog);
1108        })
1109      Button('changeZooDogAge')
1110        .onClick(()=>{
1111          // 使用this通过自定义组件内部方法调用,无法触发UI刷新
1112          this.changeZooDogAge(this.dog);
1113        })
1114      ZooChild()
1115    }
1116  }
1117}
1118
1119@Component
1120struct ZooChild {
1121
1122  build() {
1123    Column({ space:10 }) {
1124      Text(`ZooChild`)
1125        .fontColor(Color.Blue)
1126        .fontSize(30)
1127      ZooGrandChild()
1128    }
1129  }
1130}
1131
1132@Component
1133struct ZooGrandChild {
1134  @Consume dog:Animal;
1135
1136  changeZooGrandChildName(animal:Animal) {
1137    animal.name = 'Marry';
1138  }
1139
1140  build() {
1141    Column({ space:10 }) {
1142      Text(`ZooGrandChild: This is a ${this.dog.age}-year-old ${this.dog.type} named ${this.dog.name}.`)
1143        .fontColor(Color.Yellow)
1144        .fontSize(30)
1145      Button('changeName')
1146        .onClick(()=>{
1147          // 通过静态方法调用,无法触发UI刷新
1148          Animal.changeName(this.dog);
1149        })
1150      Button('changeZooGrandChildName')
1151        .onClick(()=>{
1152          // 使用this通过自定义组件内部方法调用,无法触发UI刷新
1153          this.changeZooGrandChildName(this.dog);
1154        })
1155    }
1156  }
1157}
1158```
1159
1160可以通过如下先赋值、再调用新赋值的变量的方式为this.dog保留Proxy代理,实现UI刷新。
1161
1162【正例】
1163
1164```ts
1165class Animal {
1166  name:string;
1167  type:string;
1168  age: number;
1169
1170  constructor(name:string, type:string, age:number) {
1171    this.name = name;
1172    this.type = type;
1173    this.age = age;
1174  }
1175
1176  static changeName(animal:Animal) {
1177    animal.name = 'Jack';
1178  }
1179  static changeAge(animal:Animal) {
1180    animal.age += 1;
1181  }
1182}
1183
1184@Entry
1185@Component
1186struct Zoo {
1187  @Provide dog:Animal = new Animal('WangCai', 'dog', 2);
1188
1189  changeZooDogAge(animal:Animal) {
1190    animal.age += 2;
1191  }
1192
1193  build() {
1194    Column({ space:10 }) {
1195      Text(`Zoo: This is a ${this.dog.age}-year-old ${this.dog.type} named ${this.dog.name}.`)
1196        .fontColor(Color.Red)
1197        .fontSize(30)
1198      Button('changeAge')
1199        .onClick(()=>{
1200          // 通过赋值给临时变量保留Proxy代理
1201          let newDog = this.dog;
1202          Animal.changeAge(newDog);
1203        })
1204      Button('changeZooDogAge')
1205        .onClick(()=>{
1206          // 通过赋值给临时变量保留Proxy代理
1207          let newDog = this.dog;
1208          this.changeZooDogAge(newDog);
1209        })
1210      ZooChild()
1211    }
1212  }
1213}
1214
1215@Component
1216struct ZooChild {
1217
1218  build() {
1219    Column({ space:10 }) {
1220      Text(`ZooChild.`)
1221        .fontColor(Color.Blue)
1222        .fontSize(30)
1223      ZooGrandChild()
1224    }
1225  }
1226}
1227
1228@Component
1229struct ZooGrandChild {
1230  @Consume dog:Animal;
1231
1232  changeZooGrandChildName(animal:Animal) {
1233    animal.name = 'Marry';
1234  }
1235
1236  build() {
1237    Column({ space:10 }) {
1238      Text(`ZooGrandChild: This is a ${this.dog.age}-year-old ${this.dog.type} named ${this.dog.name}.`)
1239        .fontColor(Color.Yellow)
1240        .fontSize(30)
1241      Button('changeName')
1242        .onClick(()=>{
1243          // 通过赋值给临时变量保留Proxy代理
1244          let newDog = this.dog;
1245          Animal.changeName(newDog);
1246        })
1247      Button('changeZooGrandChildName')
1248        .onClick(()=>{
1249          // 通过赋值给临时变量保留Proxy代理
1250          let newDog = this.dog;
1251          this.changeZooGrandChildName(newDog);
1252        })
1253    }
1254  }
1255}
1256```
1257