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