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 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-->