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