• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# \@Provider装饰器和\@Consumer装饰器:跨组件层级双向同步
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @liwenzhen3-->
5<!--Designer: @s10021109-->
6<!--Tester: @TerryTsao-->
7<!--Adviser: @zhang_yixin13-->
8
9\@Provider和\@Consumer用于跨组件层级数据双向同步,可以使得开发者不用拘泥于组件层级。
10\@Provider和\@Consumer属于状态管理V2装饰器,所以只能在\@ComponentV2中才能使用,在\@Component中使用会编译报错。
11
12\@Provider和\@Consumer提供了跨组件层级数据双向同步的能力。在阅读本文档前,建议提前阅读:[\@ComponentV2](./arkts-new-componentV2.md)。
13
14>**说明:**
15>
16> \@Provider和\@Consumer装饰器从API version 12开始支持。
17>
18> 从API version 12开始,\@Provider和\@Consumer装饰器支持在原子化服务中使用。
19
20## 概述
21
22\@Provider,即数据提供方,其所有的子组件都可以通过\@Consumer绑定相同的key来获取\@Provider提供的数据。
23\@Consumer,即数据消费方,可以通过绑定同样的key获取其最近父节点的\@Provider的数据,当查找不到\@Provider的数据时,使用本地默认值。图示如下。
24
25![ProviderConsumer_1](./figures/Provider_Consumer_1.png)
26
27\@Provider和\@Consumer装饰的数据类型需要一致。
28
29开发者在使用\@Provider和\@Consumer时要注意:
30- \@Provider和\@Consumer强依赖自定义组件层级,\@Consumer会因为所在组件的父组件不同,而被初始化为不同的值。
31- \@Provider和\@Consumer相当于把组件粘合在一起了,从组件独立角度考虑,应减少使用\@Provider和\@Consumer。
32
33## \@Provider和\@Consumer vs \@Provide和\@Consume能力对比
34在状态管理V1版本中,提供跨组件层级双向的装饰器为[\@Provide和\@Consume](./arkts-provide-and-consume.md),当前文档介绍的是状态管理V2装饰器\@Provider和\@Consumer。虽然两者名字和功能类似,但在特性上还存在一些差异。
35如果开发者不了解状态管理V1中的\@Provide和\@Consume,可以直接跳过本节。
36
37| 能力 | V2装饰器\@Provider和\@Consumer                                             |V1装饰器\@Provide和\@Consume|
38| ------------------ | ----------------------------------------------------- |----------------------------------------------------- |
39| \@Consume(r)         |必须本地初始化,当找不到\@Provider时使用本地默认值。| API version 20以前,@Consume禁止本地初始化,当找不到对应\@Provide的时候,会抛出异常;从API version 20开始,@Consume支持设置默认值,如果没有设置默认值,且找不到对应\@Provide时,会抛出异常。 |
40| 支持类型           | 支持function。 | 不支持function。 |
41| 观察能力           | 仅能观察自身赋值变化,如果要观察嵌套场景,配合[\@Trace](arkts-new-observedV2-and-trace.md)一起使用。 | 观察第一层变化,如果要观察嵌套场景,配合[\@Observed和\@ObjectLink](arkts-observed-and-objectlink.md)一起使用。 |
42| alias和属性名         | alias是唯一匹配的key,缺省时默认属性名为alias。 | alias和属性名都为key,优先匹配alias,匹配不到可以匹配属性名。|
43| \@Provide(r) 从父组件初始化      | 不允许。 | 允许。|
44| \@Provide(r)支持重载  | 默认开启,即\@Provider可以重名,\@Consumer向上查找最近的\@Provider。 | 默认关闭,即在组件树上不允许有同名\@Provide。如果需要重载,则需要配置allowOverride。|
45
46## 装饰器说明
47
48### 基本规则
49\@Provider语法:
50`@Provider(aliasName?: string) varName : varType = initValue`
51
52| \@Provider属性装饰器 | 说明                                                  |
53| ------------------ | ----------------------------------------------------- |
54| 装饰器参数         | `aliasName?: string`,别名,缺省时默认为属性名。|
55| 支持类型           | 自定义组件中成员变量。属性的类型可以为number、string、boolean、class、[Array](#装饰array类型变量)、[Date](#装饰date类型变量)、[Map](#装饰map类型变量)、[Set](#装饰set类型变量)等类型。支持装饰[箭头函数](#provider和consumer装饰回调事件用于组件之间完成行为抽象)。 |
56| 从父组件初始化      | 禁止。 |
57| 本地初始化         | 必须本地初始化。 |
58| 观察能力         | 能力等同于\@Trace。变化会同步给对应的\@Consumer。 |
59
60\@Consumer语法:
61`@Consumer(aliasName?: string) varName : varType = initValue`
62
63| \@Consumer属性装饰器 | 说明                                                         |
64| --------------------- | ------------------------------------------------------------ |
65| 装饰器参数            | `aliasName?: string`,别名,缺省时默认为属性名,向上查找最近的\@Provider。    |
66| 可装饰的变量          | 自定义组件中成员变量。属性的类型可以为number、string、boolean、class、Array、Date、Map、Set等类型。支持装饰箭头函数。 |
67| 从父组件初始化      | 禁止。 |
68| 本地初始化         | 必须本地初始化。 |
69| 观察能力         | 能力等同于\@Trace。变化会同步给对应的\@Provider。 |
70
71### aliasName和属性名
72
73\@Provider和\@Consumer接受可选参数aliasName,没有配置参数时,使用属性名作为默认的aliasName。
74
75>**说明:**
76>
77> aliasName是用于\@Provider和\@Consumer进行匹配的唯一指定key。
78
79以下三个例子可清楚介绍\@Provider和\@Consumer如何使用aliasName进行查找匹配。
80
81```ts
82@ComponentV2
83struct Parent {
84  // 未定义aliasName, 使用属性名'str'作为aliasName
85  @Provider() str: string = 'hello';
86}
87
88@ComponentV2
89struct Child {
90  // 定义aliasName为'str',使用aliasName去寻找
91  // 能够在Parent组件上找到, 使用@Provider的值'hello'
92  @Consumer('str') str: string = 'world';
93}
94```
95
96```ts
97@ComponentV2
98struct Parent {
99  // 定义aliasName为'alias'
100  @Provider('alias') str: string = 'hello';
101}
102
103@ComponentV2 struct Child {
104  // 定义aliasName为 'alias',找到@Provider并获得值'hello'
105  @Consumer('alias') str: string = 'world';
106}
107```
108
109```ts
110@ComponentV2
111struct Parent {
112  // 定义aliasName为'alias'
113  @Provider('alias') str: string = 'hello';
114}
115
116@ComponentV2
117struct Child {
118  // 未定义aliasName,使用属性名'str'作为aliasName
119  // 没有找到对应的@Provider,使用本地值'world'
120  @Consumer() str: string = 'world';
121}
122```
123
124## 变量传递
125
126| 传递规则       | 说明                                                         |
127| -------------- | ------------------------------------------------------------ |
128| 从父组件初始化 | \@Provider和\@Consumer装饰的变量仅允许本地初始化,不允许从外部传入初始化。 |
129| 初始化子组件   | \@Provider和\@Consumer装饰的变量可以初始化子组件中\@Param装饰的变量。 |
130
131## 使用限制
132
1331. \@Provider和\@Consumer为自定义组件的属性装饰器,只能装饰自定义组件内的属性,不能装饰class的属性。
1342. \@Provider和\@Consumer为状态管理V2装饰器,只能在\@ComponentV2中使用,不能在\@Component中使用。
1353. \@Provider和\@Consumer只支持本地初始化,不支持外部传入初始化。
136
137## 使用场景
138
139### \@Provider和\@Consumer双向同步
140
141**建立双向绑定**
142
1431. 自定义组件Parent和Child初始化:
144    - Child中`@Consumer() str: string = 'world'`向上查找,查找到Parent中声明的`@Provider() str: string = 'hello'`。
145    - `@Consumer() str: string = 'world'`初始化为其查找到的`@Provider`的值,即‘hello’。
146    - 两者建立双向同步关系。
1472. 点击Parent中的按钮,改变\@Provider装饰的str,通知其对应的\@Consumer,对应UI刷新。
1483. 点击Child中的按钮,改变\@Consumer装饰的str,通知其对应的\@Provider,对应UI刷新。
149
150```ts
151@Entry
152@ComponentV2
153struct Parent {
154  @Provider() str: string = 'hello';
155
156  build() {
157    Column() {
158      Button(this.str)
159        .onClick(() => {
160          this.str += '0';
161        })
162      Child()
163    }
164  }
165}
166
167@ComponentV2
168struct Child {
169  // @Consumer装饰的属性str和Parent组件中@Provider装饰的属性str名称相同,因此建立了双向绑定关系
170  @Consumer() str: string = 'world';
171
172  build() {
173    Column() {
174      Button(this.str)
175        .onClick(() => {
176          this.str += '0';
177        })
178    }
179  }
180}
181```
182
183**未建立双向绑定**
184
185下面的例子中,\@Provider和\@Consumer由于aliasName值不同,无法建立双向同步关系。
1861. 自定义组件Parent和Child初始化:
187    - Child中`@Consumer() str: string = 'world'`向上查找,未查找到其数据提供方@Provider。
188    - `@Consumer() str: string = 'world'`使用其本地默认值为‘world’。
189    - 两者未建立双向同步关系。
1902. 点击Parent中的按钮,改变\@Provider装饰的str1,仅刷新\@Provider关联的Button组件。
1913. 点击Child中的按钮,改变\@Consumer装饰的str,仅刷新\@Consumer关联的Button组件。
192
193```ts
194@Entry
195@ComponentV2
196struct Parent {
197  @Provider() str1: string = 'hello';
198
199  build() {
200    Column() {
201      Button(this.str1)
202        .onClick(() => {
203          this.str1 += '0';
204        })
205      Child()
206    }
207  }
208}
209
210@ComponentV2
211struct Child {
212  // @Consumer装饰的属性str和Parent组件中@Provider装饰的属性str1名称不同,无法建立双向绑定关系
213  @Consumer() str: string = 'world';
214
215  build() {
216    Column() {
217      Button(this.str)
218        .onClick(() => {
219          this.str += '0';
220        })
221    }
222  }
223}
224```
225
226### 装饰Array类型变量
227
228当装饰的对象是Array时,可以观察到Array整体的赋值,同时可以通过调用Array的接口`push`, `pop`, `shift`, `unshift`, `splice`, `copyWithin`, `fill`, `reverse`, `sort`更新Array中的数据。
229
230```ts
231@Entry
232@ComponentV2
233struct Parent {
234  @Provider() count: number[] = [1,2,3];
235
236  build() {
237    Row() {
238      Column() {
239        ForEach(this.count, (item: number) => {
240          Text(`parent: ${item}`).fontSize(30)
241          Divider()
242        })
243        Button('push').onClick(() => {
244          this.count.push(111);
245        })
246        Button('reverse').onClick(() => {
247          this.count.reverse();
248        })
249        Button('fill').onClick(() => {
250          this.count.fill(6);
251        })
252        Child()
253      }
254      .width('100%')
255    }
256    .height('100%')
257  }
258}
259
260@ComponentV2
261struct Child {
262  @Consumer() count: number[] = [9,8,7];
263
264  build() {
265    Column() {
266      ForEach(this.count, (item: number) => {
267        Text(`child: ${item}`).fontSize(30)
268        Divider()
269      })
270      Button('push').onClick(() => {
271        this.count.push(222);
272      })
273      Button('reverse').onClick(() => {
274        this.count.reverse();
275      })
276      Button('fill').onClick(() => {
277        this.count.fill(8);
278      })
279    }
280    .width('100%')
281  }
282}
283```
284
285### 装饰Date类型变量
286
287当装饰Date类型变量时,可以观察到数据源对Date整体的赋值,以及调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds`带来的变化。
288
289```ts
290@Entry
291@ComponentV2
292struct Parent {
293  @Provider() SelectedDate: Date = new Date('2021-08-08');
294
295  build() {
296    Column() {
297      Text(`parent: ${this.SelectedDate}`)
298      Button('update the new date')
299        .onClick(() => {
300          this.SelectedDate = new Date('2023-07-07');
301        })
302      Button('increase the year by 1')
303        .onClick(() => {
304          this.SelectedDate.setFullYear(this.SelectedDate.getFullYear() + 1);
305        })
306      Button('increase the month by 1')
307        .onClick(() => {
308          this.SelectedDate.setMonth(this.SelectedDate.getMonth() + 1);
309        })
310      Button('increase the day by 1')
311        .onClick(() => {
312          this.SelectedDate.setDate(this.SelectedDate.getDate() + 1);
313        })
314      Child()
315    }
316  }
317}
318@ComponentV2
319struct Child {
320  @Consumer() SelectedDate: Date = new Date('2022-07-07');
321
322  build() {
323    Column() {
324      Text(`child: ${this.SelectedDate}`)
325      Button('update the new date')
326        .onClick(() => {
327          this.SelectedDate = new Date('2025-01-01');
328        })
329      Button('increase the year by 1')
330        .onClick(() => {
331          this.SelectedDate.setFullYear(this.SelectedDate.getFullYear() + 1);
332        })
333      Button('increase the month by 1')
334        .onClick(() => {
335          this.SelectedDate.setMonth(this.SelectedDate.getMonth() + 1);
336        })
337      Button('increase the day by 1')
338        .onClick(() => {
339          this.SelectedDate.setDate(this.SelectedDate.getDate() + 1);
340        })
341    }
342  }
343}
344```
345
346### 装饰Map类型变量
347
348当装饰Map类型变量时,可以观察到数据源对Map整体的赋值,以及调用Map的接口`set`, `clear`, `delete`带来的变化。
349
350```ts
351@Entry
352@ComponentV2
353struct Parent {
354  @Provider() message: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);
355
356  build() {
357    Column() {
358      Text('Parent').fontSize(30)
359      ForEach(Array.from(this.message.entries()), (item: [number, string]) => {
360        Text(`${item[0]}`).fontSize(30)
361        Text(`${item[1]}`).fontSize(30)
362        Divider()
363      })
364      Button('init map').onClick(() => {
365        this.message = new Map([[0, 'aa'], [1, 'bb'], [3, 'cc']]);
366      })
367      Button('set new one').onClick(() => {
368        this.message.set(4, 'd');
369      })
370      Button('clear').onClick(() => {
371        this.message.clear();
372      })
373      Button('replace the first one').onClick(() => {
374        this.message.set(0, 'a~');
375      })
376      Button('delete the first one').onClick(() => {
377        this.message.delete(0);
378      })
379      Child()
380    }
381  }
382}
383@ComponentV2
384struct Child {
385  @Consumer() message: Map<number, string> = new Map([[0, 'd'], [1, 'e'], [3, 'f']]);
386
387  build() {
388    Column() {
389      Text('Child').fontSize(30)
390      ForEach(Array.from(this.message.entries()), (item: [number, string]) => {
391        Text(`${item[0]}`).fontSize(30)
392        Text(`${item[1]}`).fontSize(30)
393        Divider()
394      })
395      Button('init map').onClick(() => {
396        this.message = new Map([[0, 'dd'], [1, 'ee'], [3, 'ff']]);
397      })
398      Button('set new one').onClick(() => {
399        this.message.set(4, 'g');
400      })
401      Button('clear').onClick(() => {
402        this.message.clear();
403      })
404      Button('replace the first one').onClick(() => {
405        this.message.set(0, 'a*');
406      })
407      Button('delete the first one').onClick(() => {
408        this.message.delete(0);
409      })
410    }
411  }
412}
413```
414
415### 装饰Set类型变量
416
417当装饰Set类型变量时,可以观察到数据源对Set整体的赋值,以及调用Set的接口 `add`, `clear`, `delete`带来的变化。
418
419```ts
420@Entry
421@ComponentV2
422struct Parent {
423  @Provider() message: Set<number> = new Set([1, 2, 3, 4]);
424
425  build() {
426    Column() {
427      Text('Parent').fontSize(30)
428      ForEach(Array.from(this.message.entries()), (item: [number, number]) => {
429        Text(`${item[0]}`).fontSize(30)
430        Divider()
431      })
432      Button('init set').onClick(() => {
433        this.message = new Set([1, 2, 3, 4]);
434      })
435      Button('set new one').onClick(() => {
436        this.message.add(5);
437      })
438      Button('clear').onClick(() => {
439        this.message.clear();
440      })
441      Button('delete the first one').onClick(() => {
442        this.message.delete(1);
443      })
444      Child()
445    }
446  }
447}
448@ComponentV2
449struct Child {
450  @Consumer() message: Set<number> = new Set([1, 2, 3, 4, 5, 6]);
451
452  build() {
453    Column() {
454      Text('Child').fontSize(30)
455      ForEach(Array.from(this.message.entries()), (item: [number, number]) => {
456        Text(`${item[0]}`).fontSize(30)
457        Divider()
458      })
459      Button('init set').onClick(() => {
460        this.message = new Set([1, 2, 3, 4, 5, 6]);
461      })
462      Button('set new one').onClick(() => {
463        this.message.add(7);
464      })
465      Button('clear').onClick(() => {
466        this.message.clear();
467      })
468      Button('delete the first one').onClick(() => {
469        this.message.delete(1);
470      })
471    }
472  }
473}
474```
475
476### \@Provider和\@Consumer装饰回调事件用于组件之间完成行为抽象
477
478当需要在父组件中向子组件注册回调函数时,可以使用\@Provider和\@Consumer装饰回调方法来实现。
479在拖拽场景中,若需将子组件的拖拽起始位置信息同步给父组件,可参考以下示例。
480
481```ts
482@Entry
483@ComponentV2
484struct Parent {
485  @Local childX: number = 0;
486  @Local childY: number = 1;
487  @Provider() onDrag: (x: number, y: number) => void = (x: number, y: number) => {
488    console.info(`onDrag event at x=${x} y:${y}`);
489    this.childX = x;
490    this.childY = y;
491  }
492
493  build() {
494    Column() {
495      Text(`child position x: ${this.childX}, y: ${this.childY}`)
496      Child()
497    }
498  }
499}
500
501@ComponentV2
502struct Child {
503  @Consumer() onDrag: (x: number, y: number) => void = (x: number, y: number) => {};
504
505  build() {
506    Button('changed')
507      .draggable(true)
508      .onDragStart((event: DragEvent) => {
509        // 当前预览器上不支持通用拖拽事件
510        this.onDrag(event.getDisplayX(), event.getDisplayY());
511      })
512  }
513}
514```
515
516### \@Provider和\@Consumer装饰复杂类型,配合\@Trace一起使用
517
5181. \@Provider和\@Consumer只能观察到数据本身的变化。如果需要观察其装饰的复杂数据类型的属性变化,必须配合\@Trace一起使用。
5192. 装饰内置类型:Array、Map、Set、Date时,可以观察到某些API的变化,观察能力同[\@Trace](./arkts-new-observedV2-and-trace.md#观察变化)。
520
521```ts
522@ObservedV2
523class User {
524  @Trace name: string;
525  @Trace age: number;
526
527  constructor(name: string, age: number) {
528    this.name = name;
529    this.age = age;
530  }
531}
532const data: User[] = [new User('Json', 10), new User('Eric', 15)];
533@Entry
534@ComponentV2
535struct Parent {
536  @Provider('data') users: User[] = data;
537
538  build() {
539    Column() {
540      Child()
541      Button('add new user')
542        .onClick(() => {
543          this.users.push(new User('Molly', 18));
544        })
545      Button('age++')
546        .onClick(() => {
547          this.users[0].age++;
548        })
549      Button('change name')
550        .onClick(() => {
551          this.users[0].name = 'Shelly';
552        })
553    }
554  }
555}
556
557@ComponentV2
558struct Child {
559  @Consumer('data') users: User[] = [];
560
561  build() {
562    Column() {
563      ForEach(this.users, (item: User) => {
564        Column() {
565          Text(`name: ${item.name}`).fontSize(30)
566          Text(`age: ${item.age}`).fontSize(30)
567          Divider()
568        }
569      })
570    }
571  }
572}
573```
574
575### \@Provider重名时,\@Consumer向上查找其最近的\@Provider
576
577\@Provider可以在组件树上重名,\@Consumer会向上查找其最近父节点的\@Provider的数据。
578
579```ts
580@Entry
581@ComponentV2
582struct Index {
583  @Provider() val: number = 10;
584
585  build() {
586    Column() {
587      Parent()
588    }
589  }
590}
591
592@ComponentV2
593struct Parent {
594  @Provider() val: number = 20;
595  @Consumer('val') val2: number = 0; // 10
596
597  build() {
598    Column() {
599      Text(`${this.val2}`)
600      Child()
601    }
602  }
603}
604
605@ComponentV2
606struct Child {
607  @Consumer() val: number = 0; // 20
608
609  build() {
610    Column() {
611      Text(`${this.val}`)
612    }
613  }
614}
615```
616
617上面的例子中:
618
619- Parent中的\@Consumer向上查找,查找到Index中定义的`@Provider() val: number = 10`,初始化为10。
620- Child中的\@Consumer向上查找,查找到Parent中定义的`@Provider() val: number = 20`后停止,初始化为20。
621
622### \@Provider和\@Consumer初始化\@Param
623
624\@Provider和\@Consumer装饰的变量可以初始化子组件中\@Param装饰的变量。
625
626```ts
627@Entry
628@ComponentV2
629struct Index {
630  @Provider() val: number = 10;
631
632  build() {
633    Column() {
634      Text(`Index @Provider val: ${this.val}`).fontSize(30)
635      Parent({ val2: this.val })
636    }
637  }
638}
639
640@ComponentV2
641struct Parent {
642  @Consumer() val: number = 0;
643  @Require @Param val2: number;
644
645  build() {
646    Column() {
647      Text(`Parent @Consumer val: ${this.val}`).fontSize(30)
648      Button('change val').onClick(() => {
649        this.val++;
650      })
651      Text(`Parent @Param val2: ${this.val2}`).fontSize(30)
652      Child({ val: this.val })
653    }.border({ width: 2, color: Color.Green })
654  }
655}
656
657@ComponentV2
658struct Child {
659  @Require @Param val: number;
660
661  build() {
662    Column() {
663      Text(`Child @Param val ${this.val}`).fontSize(30)
664    }.border({ width: 2, color: Color.Pink })
665  }
666}
667```
668
669上面的例子中:
670
671- Index中\@Provider装饰的变量val与Parent中\@Consumer装饰的变量val建立双向数据绑定。Parent中\@Param装饰的变量val2接收Index中数据源val的数据,并同步其变化。Child中\@Param装饰的变量val接收Parent中数据源val的数据,并同步其变化。
672- 点击Parent中的按钮,触发`@Consumer() val`的变化,变化同步给Index中的`@Provider() val`和Child中的`@Param val`,对应UI刷新。
673- Index中`@Provider() val`的变化同步给Parent中的`@Param val2`,对应UI刷新。
674<!--no_check-->