• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# \@Provide装饰器和\@Consume装饰器:与后代组件双向同步
2
3
4\@Provide和\@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,\@Provide和\@Consume摆脱参数传递机制的束缚,实现跨层级传递。
5
6
7其中\@Provide装饰的变量是在祖先组件中,可以理解为被“提供”给后代的状态变量。\@Consume装饰的变量是在后代组件中,去“消费(绑定)”祖先组件提供的变量。
8
9
10> **说明:**
11>
12> 从API version 9开始,这两个装饰器支持在ArkTS卡片中使用。
13
14
15## 概述
16
17\@Provide/\@Consume装饰的状态变量有以下特性:
18
19- \@Provide装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。由此可见,\@Provide的方便之处在于,开发者不需要多次在组件之间传递变量。
20
21- 后代通过使用\@Consume去获取\@Provide提供的变量,建立在\@Provide和\@Consume之间的双向数据同步,与\@State/\@Link不同的是,前者可以在多层级的父子组件之间传递。
22
23- \@Provide和\@Consume可以通过相同的变量名或者相同的变量别名绑定,建议类型相同,否则会发生类型隐式转换,从而导致应用行为异常。
24
25
26```ts
27// 通过相同的变量名绑定
28@Provide a: number = 0;
29@Consume a: number;
30
31// 通过相同的变量别名绑定
32@Provide('a') b: number = 0;
33@Consume('a') c: number;
34```
35
36
37\@Provide和\@Consume通过相同的变量名或者相同的变量别名绑定时,\@Provide修饰的变量和\@Consume修饰的变量是一对多的关系。不允许在同一个自定义组件内,包括其子组件中声明多个同名或者同别名的\@Provide装饰的变量,@Provide的属性名或别名需要唯一且确定,如果声明多个同名或者同别名的@Provide装饰的变量,会发生运行时报错。
38
39
40## 装饰器说明
41
42\@State的规则同样适用于\@Provide,差异为\@Provide还作为多层后代的同步源。
43
44| \@Provide变量装饰器 | 说明                                       |
45| -------------- | ---------------------------------------- |
46| 装饰器参数          | 别名:常量字符串,可选。<br/>如果指定了别名,则通过别名来绑定变量;如果未指定别名,则通过变量名绑定变量。 |
47| 同步类型           | 双向同步。<br/>从\@Provide变量到所有\@Consume变量以及相反的方向的数据同步。双向同步的操作与\@State和\@Link的组合相同。 |
48| 允许装饰的变量类型      | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>支持Date类型。<br/>支持类型的场景请参考[观察变化](#观察变化)。<br/>支持上述支持类型的联合类型,比如string \| number, string \| undefined 或者 ClassA \| null,示例见[@Provide_and_Consume支持联合类型实例](#provide_and_consume支持联合类型实例)。 <br/>**注意**<br/>当使用undefined和null的时候,建议显式指定类型,遵循TypeScipt类型校验,比如:`@Provide a : string \| undefined = undefiend`是推荐的,不推荐`@Provide a: string = undefined`。
49<br/>支持AkrUI框架定义的联合类型Length、ResourceStr、ResourceColor类型。<br/>不支持any。<br/>必须指定类型。\@Provide变量的\@Consume变量的类型必须相同。|
50| 被装饰变量的初始值      | 必须指定。                                    |
51
52| \@Consume变量装饰器 | 说明                                       |
53| -------------- | ---------------------------------------- |
54| 装饰器参数          | 别名:常量字符串,可选。<br/>如果提供了别名,则必须有\@Provide的变量和其有相同的别名才可以匹配成功;否则,则需要变量名相同才能匹配成功。 |
55| 同步类型           | 双向:从\@Provide变量(具体请参见\@Provide)到所有\@Consume变量,以及相反的方向。双向同步操作与\@State和\@Link的组合相同。 |
56| 允许装饰的变量类型      | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>支持Date类型。<br/>支持类型的场景请参考[观察变化](#观察变化)<br/>支持类型的场景请参考[观察变化](#观察变化)。<br/>支持上述支持类型的联合类型,比如string \| number, string \| undefined 或者 ClassA \| null,示例见[@Provide_and_Consume支持联合类型实例](#provide_and_consume支持联合类型实例)。 <br/>**注意**<br/>当使用undefined和null的时候,建议显式指定类型,遵循TypeScipt类型校验,比如:`@Consume a : string \| undefined`是推荐的,不推荐`@Consume a: string`。
57<br/>支持AkrUI框架定义的联合类型Length、ResourceStr、ResourceColor类型。<br/>不支持any。<br/>必须指定类型。\@Provide变量的\@Consume变量的类型必须相同。<br/>**说明:**<br/>\@Consume装饰的变量,在其父组件或者祖先组件上,必须有对应的属性和别名的\@Provide装饰的变量。 |
58| 被装饰变量的初始值      | 无,禁止本地初始化。                               |
59
60
61## 变量的传递/访问规则说明
62
63
64| \@Provide传递/访问 | 说明                                       |
65| -------------- | ---------------------------------------- |
66| 从父组件初始化和更新     | 可选,允许父组件中常规变量(常规变量对@Prop赋值,只是数值的初始化,常规变量的变化不会触发UI刷新,只有状态变量才能触发UI刷新)、\@State、\@Link、\@Prop、\@Provide、\@Consume、\@ObjectLink、\@StorageLink、\@StorageProp、\@LocalStorageLink和\@LocalStorageProp装饰的变量装饰变量初始化子组件\@Provide。 |
67| 用于初始化子组件       | 允许,可用于初始化\@State、\@Link、\@Prop、\@Provide。 |
68| 和父组件同步         | 否。                                       |
69| 和后代组件同步        | 和\@Consume双向同步。                          |
70| 是否支持组件外访问      | 私有,仅可以在所属组件内访问。                          |
71
72
73  **图1** \@Provide初始化规则图示  
74
75
76![zh-cn_image_0000001552614217](figures/zh-cn_image_0000001552614217.png)
77
78
79| \@Consume传递/访问 | 说明                                       |
80| -------------- | ---------------------------------------- |
81| 从父组件初始化和更新     | 禁止。通过相同的变量名和alias(别名)从\@Provide初始化。      |
82| 用于初始化子组件       | 允许,可用于初始化\@State、\@Link、\@Prop、\@Provide。 |
83| 和祖先组件同步        | 和\@Provide双向同步。                          |
84| 是否支持组件外访问      | 私有,仅可以在所属组件内访问                           |
85
86
87  **图2** \@Consume初始化规则图示  
88
89
90![zh-cn_image_0000001502094666](figures/zh-cn_image_0000001502094666.png)
91
92
93## 观察变化和行为表现
94
95
96### 观察变化
97
98- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
99
100- 当装饰的数据类型为class或者Object的时候,可以观察到赋值和属性赋值的变化(属性为Object.keys(observedObject)返回的所有属性)。
101
102- 当装饰的对象是array的时候,可以观察到数组的添加、删除、更新数组单元。
103
104- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性。
105
106```ts
107@Component
108struct CompD {
109
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 CompA {
135
136  @Provide selectedDate: Date = new Date('2021-08-08')
137
138  build() {
139    Column() {
140      Button('parent increase the day by 1')
141        .margin(10)
142        .onClick(() => {
143          this.selectedDate.setDate(this.selectedDate.getDate() + 1)
144        })
145      Button('parent update the new date')
146        .margin(10)
147        .onClick(() => {
148          this.selectedDate = new Date('2023-07-07')
149        })
150      DatePicker({
151        start: new Date('1970-1-1'),
152        end: new Date('2100-1-1'),
153        selected: this.selectedDate
154      })
155      CompD()
156    }
157  }
158}
159```
160
161### 框架行为
162
1631. 初始渲染:
164   1. \@Provide装饰的变量会以map的形式,传递给当前\@Provide所属组件的所有子组件;
165   2. 子组件中如果使用\@Consume变量,则会在map中查找是否有该变量名/alias(别名)对应的\@Provide的变量,如果查找不到,框架会抛出JS ERROR;
166   3. 在初始化\@Consume变量时,和\@State/\@Link的流程类似,\@Consume变量会保存在map中查找到的\@Provide变量,并把自己注册给\@Provide。
167
1682. 当\@Provide装饰的数据变化时:
169   1. 通过初始渲染的步骤可知,子组件\@Consume已把自己注册给父组件。父组件\@Provide变量变更后,会遍历更新所有依赖它的系统组件(elementid)和状态变量(\@Consume);
170   2. 通知\@Consume更新后,子组件所有依赖\@Consume的系统组件(elementId)都会被通知更新。以此实现\@Provide对\@Consume状态数据同步。
171
1723. 当\@Consume装饰的数据变化时:
173
174   通过初始渲染的步骤可知,子组件\@Consume持有\@Provide的实例。在\@Consume更新后调用\@Provide的更新方法,将更新的数值同步回\@Provide,以此实现\@Consume向\@Provide的同步更新。
175
176
177## 使用场景
178
179在下面的示例是与后代组件双向同步状态\@Provide和\@Consume场景。当分别点击CompA和CompD组件内Button时,reviewVotes 的更改会双向同步在CompA和CompD中。
180
181
182
183```ts
184@Component
185struct CompD {
186  // @Consume装饰的变量通过相同的属性名绑定其祖先组件CompA内的@Provide装饰的变量
187  @Consume reviewVotes: number;
188
189  build() {
190    Column() {
191      Text(`reviewVotes(${this.reviewVotes})`)
192      Button(`reviewVotes(${this.reviewVotes}), give +1`)
193        .onClick(() => this.reviewVotes += 1)
194    }
195    .width('50%')
196  }
197}
198
199@Component
200struct CompC {
201  build() {
202    Row({ space: 5 }) {
203      CompD()
204      CompD()
205    }
206  }
207}
208
209@Component
210struct CompB {
211  build() {
212    CompC()
213  }
214}
215
216@Entry
217@Component
218struct CompA {
219  // @Provide装饰的变量reviewVotes由入口组件CompA提供其后代组件
220  @Provide reviewVotes: number = 0;
221
222  build() {
223    Column() {
224      Button(`reviewVotes(${this.reviewVotes}), give +1`)
225        .onClick(() => this.reviewVotes += 1)
226      CompB()
227    }
228  }
229}
230```
231
232## Provide_and_Consume支持联合类型实例
233
234@Provide和@Consume支持联合类型和undefined和null,在下面的示例中,count类型为string | undefined,点击父组件Parent中的Button改变count的属性或者类型,Child中也会对应刷新。
235
236```ts
237@Component
238struct Child {
239  // @Consume装饰的变量通过相同的属性名绑定其祖先组件Ancestors内的@Provide装饰的变量
240  @Consume count: string | undefined;
241
242  build() {
243    Column() {
244      Text(`count(${this.count})`)
245      Button(`count(${this.count}), Child`)
246        .onClick(() => this.count = 'Ancestors')
247    }
248    .width('50%')
249  }
250}
251
252@Component
253struct Parent {
254  build() {
255    Row({ space: 5 }) {
256      Child()
257    }
258  }
259}
260
261@Entry
262@Component
263struct Ancestors {
264  // @Provide装饰的联合类型count由入口组件Ancestors提供其后代组件
265  @Provide count: string | undefined = 'Child';
266
267  build() {
268    Column() {
269      Button(`count(${this.count}), Child`)
270        .onClick(() => this.count = undefined)
271      Parent()
272    }
273  }
274}
275```
276