1# \@Provider装饰器和\@Consumer装饰器:跨组件层级双向同步
2
3\@Provider和\@Consumer用于跨组件层级数据双向同步,可以使得开发者不用拘泥于组件层级。
4\@Provider和\@Consumer属于状态管理V2装饰器,所以只能在\@ComponentV2中才能使用,在\@Component中使用会编译报错。
5
6
7\@Provider和\@Consumer提供了跨组件层级数据双向同步的能力。在阅读本文档前,建议提前阅读:[\@ComponentV2](./arkts-new-componentV2.md)。
8
9>**说明:**
10>
11>\@Provider和\@Consumer装饰器从API version 12开始支持。
12>
13
14## 概述
15
16\@Provider,即数据提供方,其所有的子组件都可以通过\@Consumer绑定相同的key来获取\@Provider提供的数据。
17\@Consumer,即数据消费方,可以通过绑定同样的key获取其最近父节点的\@Provider的数据,当查找不到\@Provider的数据时,使用本地默认值。
18\@Provider和\@Consumer装饰数据类型需要一致。
19
20
21开发者在使用\@Provider和\@Consumer时要注意:
22- \@Provider和\@Consumer强依赖自定义组件层级,\@Consumer会因为所在组件的父组件不同,而被初始化为不同的值。
23- \@Provider和\@Consumer相当于把组件粘合在一起了,从组件独立角度,要减少使用\@Provider和\@Consumer。
24
25
26## \@Provider和\@Consumer vs \@Provide和\@Consume能力对比
27在状态管理V1版本中,提供跨组件层级双向的装饰器为[\@Provide和\@Consume](./arkts-provide-and-consume.md),当前文档介绍的是状态管理V2装饰器\@Provider和\@Consumer。虽然两者名字和功能类似,但在特性上还存在一些差异。
28如果开发者对状态管理V1中\@Provide和\@Consume完全不曾了解过,可以直接跳过本节。
29
30| 能力 | V2装饰器\@Provider和\@Consumer                                             |V1装饰器\@Provide和\@Consume|
31| ------------------ | ----------------------------------------------------- |----------------------------------------------------- |
32| \@Consume(r)         |允许本地初始化,当找不到\@Provider的时候使用本地默认值。| 禁止本地初始化,当找不到对应的的\@Provide时候,会抛出异常。 |
33| 支持类型           | 支持function。 | 不支持function。 |
34| 观察能力           | 仅能观察自身赋值变化,如果要观察嵌套场景,配合[\@Trace](arkts-new-observedV2-and-trace.md)一起使用。 | 观察第一层变化,如果要观察嵌套场景,配合[\@Observed和\@ObjectLink](arkts-observed-and-objectlink.md)一起使用。 |
35| alias和属性名         | alias是唯一匹配的key,如果缺省alias,则默认属性名为alias。 | alias和属性名都为key,优先匹配alias,匹配不到可以匹配属性名。|
36| \@Provide(r) 从父组件初始化      | 禁止。 | 允许。|
37| \@Provide(r)支持重载  | 默认开启,即\@Provider可以重名,\@Consumer向上查找最近的\@Provider。 | 默认关闭,即在组件树上不允许有同名\@Provide。如果需要重载,则需要配置allowOverride。|
38
39
40## 装饰器说明
41
42### 基本规则
43\@Provider语法:
44`@Provider(alias?: string) varName : varType = initValue`
45
46| \@Provider属性装饰器 | 说明                                                  |
47| ------------------ | ----------------------------------------------------- |
48| 装饰器参数         | `aliasName?: string`,别名,缺省时默认为属性名。|
49| 支持类型           | 自定义组件中成员变量。属性的类型可以为number、string、boolean、class、Array、Date、Map、Set等类型。支持装饰[箭头函数](#provider和consumer装饰回调事件用于组件之间完成行为抽象)。 |
50| 从父组件初始化      | 禁止。 |
51| 本地初始化         | 必须本地初始化。 |
52| 观察能力         | 能力等同于\@Trace。变化会同步给对应的\@Consumer。 |
53
54\@Consumer语法:
55`@Consumer(alias?: string) varName : varType = initValue`
56
57
58| \@Consumer属性装饰器 | 说明                                                         |
59| --------------------- | ------------------------------------------------------------ |
60| 装饰器参数            | `aliasName?: string`,别名,缺省时默认为属性名,向上查找最近的\@Provider。    |
61| 可装饰的变量          | 自定义组件中成员变量。属性的类型可以为number、string、boolean、class、Array、Date、Map、Set等类型。支持装饰箭头函数。 |
62| 从父组件初始化      | 禁止。 |
63| 本地初始化         | 必须本地初始化。 |
64| 观察能力         | 能力等同于\@Trace。变化会同步给对应的\@Provider。 |
65
66### aliasName和属性名
67\@Provider和\@Consumer可接受可选参数aliasName,如果开发者没有配置参数,则使用属性名作为默认的aliasName。注意:aliasName是用于\@Provider和\@Consumer进行匹配的唯一指定key。
68
69以下三个例子可清楚介绍\@Provider和\@Consumer如何使用aliasName进行查找匹配。
70```ts
71@ComponentV2
72struct Parent {
73  // 未定义aliasName, 使用属性名'str'作为aliasName
74  @Provider() str: string = 'hello';
75}
76
77@ComponentV2
78struct Child {
79  // 定义aliasName为'str',使用aliasName去寻找
80  // 能够在Parent组件上找到, 使用@Provider的值'hello'
81  @Consumer('str') str: string = 'world';
82}
83```
84
85```ts
86@ComponentV2
87struct Parent {
88  // 定义aliasName为'alias'
89  @Provider('alias') str: string = 'hello';
90}
91
92@ComponentV2 struct Child {
93  // 定义aliasName为 'alias',找到@Provider并获得值'hello'
94  @Consumer('alias') str: string = 'world';
95}
96```
97
98```ts
99@ComponentV2
100struct Parent {
101  // 定义aliasName为'alias'
102  @Provider('alias') str: string = 'hello';
103}
104
105@ComponentV2
106struct Child {
107  // 未定义aliasName,使用属性名'str'作为aliasName
108  // 没有找到对应的@Provider,使用本地值'world'
109  @Consumer() str: string = 'world';
110}
111```
112
113## 变量传递
114
115| 传递规则       | 说明                                                         |
116| -------------- | ------------------------------------------------------------ |
117| 从父组件初始化 | \@Provider和\@Consumer装饰的变量仅允许本地初始化,无法从外部传入初始化。 |
118| 初始化子组件   | \@Provider和\@Consumer装饰的变量可以初始化子组件中\@Param装饰的变量。 |
119
120## 使用限制
121
1221. \@Provider和\@Consumer为自定义组件的属性装饰器,仅能装饰自定义组件内的属性,不能装饰class的属性。
1232. \@Provider和\@Consumer为状态管理V2装饰器,只能在\@ComponentV2中使用,不能在\@Component中使用。
1243. \@Provider和\@Consumer仅支持本地初始化,不支持外部传入初始化。
125
126## 使用场景
127
128### \@Provider和\@Consumer双向同步
129#### 建立双向绑定
1301. 自定义组件Parent和Child初始化:
131    - Child中`@Consumer() str: string = 'world'`向上查找,查找到Parent中声明的`@Provider() str: string = 'hello'`。
132    - `@Consumer() str: string = 'world'`初始化为其查找到的`@Provider`的值,即‘hello’。
133    - 两者建立双向同步关系。
1342. 点击Parent中的Button,改变\@Provider装饰的str,通知其对应的\@Consumer,对应UI刷新。
1353. 点击Child中Button,改变\@Consumer装饰的str,通知其对应的\@Provider,对应UI刷新。
136
137```ts
138@Entry
139@ComponentV2
140struct Parent {
141  @Provider() str: string = 'hello';
142
143  build() {
144    Column() {
145      Button(this.str)
146        .onClick(() => {
147          this.str += '0';
148        })
149      Child()
150    }
151  }
152}
153
154@ComponentV2
155struct Child {
156  @Consumer() str: string = 'world';
157
158  build() {
159    Column() {
160      Button(this.str)
161        .onClick(() => {
162          this.str += '0';
163        })
164    }
165  }
166}
167```
168#### 未建立双向绑定
169
170下面的例子中,\@Provider和\@Consumer由于aliasName值不同,无法建立双向同步关系。
1711. 自定义组件Parent和Child初始化:
172    - Child中`@Consumer() str: string = 'world'`向上查找,未查找到其数据提供方@Provider。
173    - `@Consumer() str: string = 'world'`使用其本地默认值为‘world’。
174    - 两者未建立双向同步关系。
1752. 点击Parent中的Button,改变\@Provider装饰的str1,仅刷新\@Provider关联的Button组件。
1763. 点击Child中Button,改变\@Consumer装饰的str,仅刷新\@Consumer关联的Button组件。
177
178```ts
179@Entry
180@ComponentV2
181struct Parent {
182  @Provider() str1: string = 'hello';
183
184  build() {
185    Column() {
186      Button(this.str1)
187        .onClick(() => {
188          this.str1 += '0';
189        })
190      Child()
191    }
192  }
193}
194
195@ComponentV2
196struct Child {
197  @Consumer() str: string = 'world';
198
199  build() {
200    Column() {
201      Button(this.str)
202        .onClick(() => {
203          this.str += '0';
204        })
205    }
206  }
207}
208```
209
210### \@Provider和\@Consumer装饰回调事件,用于组件之间完成行为抽象
211
212当需要在父组件中向子组件注册回调函数时,可以通过使用\@Provider和\@Consumer装饰回调方法来解决。
213比如拖拽场景,当发生拖拽事件时,如果希望将子组件拖拽的起始位置信息同步给父组件,可以参考下面的例子。
214
215```ts
216@Entry
217@ComponentV2
218struct Parent {
219  @Local childX: number = 0;
220  @Local childY: number = 1;
221  @Provider() onDrag: (x: number, y: number) => void = (x: number, y: number) => {
222    console.log(`onDrag event at x=${x} y:${y}`);
223    this.childX = x;
224    this.childY = y;
225  }
226
227  build() {
228    Column() {
229      Text(`child position x: ${this.childX}, y: ${this.childY}`)
230      Child()
231    }
232  }
233}
234
235@ComponentV2
236struct Child {
237  @Consumer() onDrag: (x: number, y: number) => void = (x: number, y: number) => {};
238
239  build() {
240    Button("changed")
241      .draggable(true)
242      .onDragStart((event: DragEvent) => {
243        // 当前预览器上不支持通用拖拽事件
244        this.onDrag(event.getDisplayX(), event.getDisplayY());
245      })
246  }
247}
248```
249
250
251### \@Provider和\@Consumer装饰复杂类型,配合\@Trace一起使用
252
2531. \@Provider和\@Consumer只能观察到数据本身的变化。如果当其装饰复杂数据类型,需要观察属性的变化时,需要配合\@Trace一起使用。
2542. 装饰内置类型:Array、Map、Set、Date时,可以观察到某些API的变化,观察能力同[\@Trace](./arkts-new-observedV2-and-trace.md#观察变化)。
255
256```ts
257@ObservedV2
258class User {
259  @Trace name: string;
260  @Trace age: number;
261
262  constructor(name: string, age: number) {
263    this.name = name;
264    this.age = age;
265  }
266}
267const data: User[] = [new User('Json', 10), new User('Eric', 15)];
268@Entry
269@ComponentV2
270struct Parent {
271  @Provider('data') users: User[] = data;
272
273  build() {
274    Column() {
275      Child()
276      Button('add new user')
277        .onClick(() => {
278          this.users.push(new User('Molly', 18));
279        })
280      Button('age++')
281        .onClick(() => {
282          this.users[0].age++;
283        })
284      Button('change name')
285        .onClick(() => {
286          this.users[0].name = 'Shelly';
287        })
288    }
289  }
290}
291
292@ComponentV2
293struct Child {
294  @Consumer('data') users: User[] = [];
295
296  build() {
297    Column() {
298      ForEach(this.users, (item: User) => {
299        Column() {
300          Text(`name: ${item.name}`).fontSize(30)
301          Text(`age: ${item.age}`).fontSize(30)
302          Divider()
303        }
304      })
305    }
306  }
307}
308```
309
310### \@Provider重名时,\@Consumer向上查找其最近的\@Provider
311\@Provider可以在组件树上重名,\@Consumer会向上查找其最近父节点的\@Provider的数据。
312```ts
313@Entry
314@ComponentV2
315struct Index {
316  @Provider() val: number = 10;
317
318  build() {
319    Column() {
320      Parent()
321    }
322  }
323}
324
325@ComponentV2
326struct Parent {
327  @Provider() val: number = 20;
328  @Consumer("val") val2: number = 0; // 10
329
330  build() {
331    Column() {
332      Text(`${this.val2}`)
333      Child()
334    }
335  }
336}
337
338@ComponentV2
339struct Child {
340  @Consumer() val: number = 0; // 20
341
342  build() {
343    Column() {
344      Text(`${this.val}`)
345    }
346  }
347}
348```
349
350上面的例子中:
351
352- Parent中\@Consumer向上查找,查找到Index中定义的` @Provider() val: number = 10`,所以初始化为10。
353- Child中\@Consumer向上查找,查找到Parent中定义的`@Provider() val: number = 20`后停止,不会继续向上查找,所以初始化为20。
354
355### \@Provider和\@Consumer初始化\@Param
356
357- 点击Text(\`Parent @Consumer val: ${this.val}\`),触发`@Consumer() val`的变化,变化同步给Index中`@Provider() val`,从而触发子组件`Text(Parent @Param val2: ${this.val2})`的刷新。
358- `Parent @Consumer() val`的变化也会同步给Child,触发`Text(Child @Param val ${this.val})`的刷新。
359
360```ts
361@Entry
362@ComponentV2
363struct Index {
364  @Provider() val: number = 10;
365
366  build() {
367    Column() {
368      Parent({ val2: this.val })
369    }
370  }
371}
372
373@ComponentV2
374struct Parent {
375  @Consumer() val: number = 0;
376  @Param val2: number = 0;
377
378  build() {
379    Column() {
380      Text(`Parent @Consumer val: ${this.val}`).fontSize(30).onClick(() => {
381        this.val++;
382      })
383      Text(`Parent @Param val2: ${this.val2}`).fontSize(30)
384      Child({ val: this.val })
385    }.border({ width: 2, color: Color.Green })
386  }
387}
388
389@ComponentV2
390struct Child {
391  @Param val: number = 0;
392
393  build() {
394    Column() {
395      Text(`Child @Param val ${this.val}`).fontSize(30)
396    }.border({ width: 2, color: Color.Pink })
397  }
398}
399```
400<!--no_check-->