1# \@Provide装饰器和\@Consume装饰器:与后代组件双向同步 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @liwenzhen3--> 5<!--Designer: @s10021109--> 6<!--Tester: @TerryTsao--> 7<!--Adviser: @zhang_yixin13--> 8 9\@Provide和\@Consume,应用于与后代组件的双向数据同步、状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,\@Provide和\@Consume摆脱参数传递机制的束缚,实现跨层级传递。 10 11其中\@Provide装饰的变量是在祖先组件中,可以理解为被“提供”给后代的状态变量。\@Consume装饰的变量是在后代组件中,去“消费(绑定)”祖先组件提供的变量。 12 13\@Provide/\@Consume是跨组件层级的双向同步。在阅读\@Provide和\@Consume文档前,建议开发者对UI范式基本语法和自定义组件有基本的了解。建议提前阅读:[基本语法概述](./arkts-basic-syntax-overview.md),[声明式UI描述](./arkts-declarative-ui-description.md),[自定义组件-创建自定义组件](./arkts-create-custom-components.md)。最佳实践请参考[状态管理最佳实践](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-status-management)。 14 15> **说明:** 16> 17> 从API version 9开始,这两个装饰器支持在ArkTS卡片中使用。 18> 19> 从API version 11开始,这两个装饰器支持在原子化服务中使用。 20> 21>API version 19及以前,\@Provide和\@Consume双向同步仅支持声明式节点场景。 22> 23> 从API version 20开始,@Consume装饰的变量支持设置默认值。当查找不到@Provide的匹配结果时,@Consume装饰的变量会使用默认值进行初始化;当查找到@Provide的匹配结果时,@Consume装饰的变量会优先使用@Provide匹配结果的值,默认值不生效。 24> 25> 从API version 20开始,通过配置[BuilderNode](../../reference/apis-arkui/js-apis-arkui-builderNode.md)的[BuildOptions](../../reference/apis-arkui/js-apis-arkui-builderNode.md#buildoptions12)参数`enableProvideConsumeCrossing`为true,使得\@Provide和\@Consume支持跨[BuilderNode](../../reference/apis-arkui/js-apis-arkui-builderNode.md)双向同步。但需要注意,BuilderNode会在上树前构造节点,所以BuilderNode内部定义的\@Consume需要设置默认值,并在BuilderNode上树后,重新获取最近的\@Provide数据,与之建立双向同步关系。具体可见[\@Consume在跨BuilderNode场景下和\@Provide建立双向同步](#consume在跨buildernode场景下和provide建立双向同步)。 26 27## 概述 28 29\@Provide/\@Consume装饰的状态变量有以下特性: 30 31- \@Provide装饰的状态变量自动对其所有后代组件可用,开发者不需要多次在组件之间传递变量。 32 33- 后代通过使用\@Consume去获取\@Provide提供的变量,建立在\@Provide和\@Consume之间的双向数据同步,与\@State/\@Link不同的是,前者可以更便捷的在多层级父子组件之间传递。 34 35- \@Provide和\@Consume通过变量名或者变量别名绑定,需要类型相同,否则会发生类型隐式转换,从而导致应用行为异常。 36 37```ts 38// 通过相同的变量名绑定 39@Provide age: number = 0; 40@Consume age: number; 41 42// 通过相同的变量别名绑定 43@Provide('a') id: number = 0; 44@Consume('a') age: number; 45 46// 通过Provide的变量别名和Consume的变量名相同绑定 47@Provide('a') id: number = 0; 48@Consume a: number; 49 50// 通过Provide的变量名和Consume的变量别名绑定 51@Provide id: number = 0; 52@Consume('id') a: number; 53 54``` 55当\@Provide指定变量别名时,会同时保存变量名与变量别名,\@Consume在查找时,会优先以变量别名作为查找值去匹配,如果没有别名则用变量名作为查找值,只要\@Consume提供的查找值与\@Provide保存的变量名或别名中任意一项一致,即可成功建立绑定关系。 56 57## 装饰器说明 58 59\@State的规则同样适用于\@Provide,差异为\@Provide还作为多层后代的同步源。 60 61| \@Provide变量装饰器 | 说明 | 62| -------------- | ---------------------------------------- | 63| 装饰器参数 | 别名:常量字符串,可选。| 64| 同步类型 | 双向同步。<br/>从\@Provide变量到所有\@Consume变量以及相反的方向的数据同步。双向同步的操作与\@State和\@Link的组合相同。 | 65| 允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>API version 10开始支持[Date类型](#装饰date类型变量)。<br/>API version 11及以上支持[Map](#装饰map类型变量)、[Set](#装饰set类型变量)类型、undefined和null类型、ArkUI框架定义的联合类型[Length](../../reference/apis-arkui/arkui-ts/ts-types.md#length)、[ResourceStr](../../reference/apis-arkui/arkui-ts/ts-types.md#resourcestr)、[ResourceColor](../../reference/apis-arkui/arkui-ts/ts-types.md#resourcecolor)类型以及这些类型的联合类型,示例见[@Provide和Consume支持联合类型实例](#provide和consume支持联合类型实例)。<br/>支持类型的场景请参考[观察变化](#观察变化)。| 66| 不允许装饰的变量类型 | 不支持装饰Function类型。 | 67| 被装饰变量的初始值 | 必须本地初始化。 | 68| 支持allowOverride参数 | 允许重写,只要声明了allowOverride,则别名和属性名都可以被Override。示例见[\@Provide支持allowOverride参数](#provide支持allowoverride参数)。 | 69 70| \@Consume变量装饰器 | 说明 | 71| -------------- | ---------------------------------------- | 72| 装饰器参数 | 别名:常量字符串,可选。 | 73| 同步类型 | 双向同步:从\@Provide变量(具体请参见\@Provide)到所有\@Consume变量,以及相反的方向。双向同步操作与\@State和\@Link的组合相同。 | 74| 允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>API version 10开始支持[Date类型](#装饰date类型变量)。<br/>API version 11及以上支持[Map](#装饰map类型变量)、[Set](#装饰set类型变量)类型、undefined和null类型、ArkUI框架定义的联合类型[Length](../../reference/apis-arkui/arkui-ts/ts-types.md#length)、[ResourceStr](../../reference/apis-arkui/arkui-ts/ts-types.md#resourcestr)、[ResourceColor](../../reference/apis-arkui/arkui-ts/ts-types.md#resourcecolor)类型以及这些类型的联合类型,示例见[@Provide和Consume支持联合类型实例](#provide和consume支持联合类型实例)。<br/>支持类型的场景请参考[观察变化](#观察变化)。 <br/>**说明:** <br/>API version 20之前,\@Consume装饰的变量,在其父组件或者祖先组件上,必须有对应的属性和别名的\@Provide装饰的变量。 75| 被装饰变量的初始值 | 从API version 20开始,\@Consume支持设置默认值。若存在匹配成功的\@Provide,则会使用\@Provide的变量值作为初始值。示例见[\@Consume装饰的变量支持设置默认值](#consume装饰的变量支持设置默认值)。 | 76 77## 变量的传递/访问规则说明 78 79| \@Provide传递/访问 | 说明 | 80| -------------- | ---------------------------------------- | 81| 从父组件初始化和更新 | 可选,允许父组件中常规变量(常规变量对@Provide赋值,只是数值的初始化,常规变量的变化不会触发UI刷新,只有状态变量才能触发UI刷新)、[\@State](./arkts-state.md)、[\@Link](./arkts-link.md)、[\@Prop](./arkts-prop.md)、\@Provide、\@Consume、[\@ObjectLink](./arkts-observed-and-objectlink.md)、[\@StorageLink](./arkts-appstorage.md#storagelink)、[\@StorageProp](./arkts-appstorage.md#storageprop)、[\@LocalStorageLink](./arkts-localstorage.md#localstoragelink)和[\@LocalStorageProp](./arkts-localstorage.md#localstorageprop)装饰的变量装饰变量初始化子组件\@Provide。 | 82| 用于初始化子组件 | 允许,可用于初始化\@State、\@Link、\@Prop、\@Provide。 | 83| 和父组件同步 | 否。 | 84| 和后代组件同步 | 和\@Consume双向同步。 | 85| 是否支持组件外访问 | 私有,仅可以在所属组件内访问。 | 86 87 **图1** \@Provide初始化规则图示 88 89 90 91| \@Consume传递/访问 | 说明 | 92| -------------- | ---------------------------------------- | 93| 从父组件初始化和更新 | 禁止。 | 94| 用于初始化子组件 | 允许,可用于初始化\@State、\@Link、\@Prop、\@Provide。 | 95| 和祖先组件同步 | 和\@Provide双向同步。 | 96| 是否支持组件外访问 | 私有,仅可以在所属组件内访问 | 97 98 **图2** \@Consume初始化规则图示 99 100 101 102 103## 观察变化和行为表现 104 105### 观察变化 106 107- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。 108 109- 当装饰的数据类型为class或者Object的时候,可以观察到赋值和属性赋值的变化(属性为Object.keys(observedObject)返回的所有属性)。 110 111- 当装饰Array时,可以观察到数组本身、数组项的赋值及其API操作带来的变化。 112 113- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性,详见[装饰Date类型变量](#装饰date类型变量)。 114 115- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口`set`, `clear`, `delete` 更新Map的值。详见[装饰Map类型变量](#装饰map类型变量)。 116 117- 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口`add`, `clear`, `delete` 更新Set的值。详见[装饰Set类型变量](#装饰set类型变量)。 118 119### 框架行为 120 1211. 初始渲染: 122 1. \@Provide装饰的变量会以Map的形式,传递给当前\@Provide所属组件的所有子组件。 123 2. 子组件中如果使用\@Consume变量,则会在Map中查找是否有该变量名/alias(别名)对应的\@Provide的变量。在API version 20之前,如果查找不到,框架会抛出JS ERROR。从API version 20开始,如果查找不到,会判断\@Consume装饰的变量是否设置了默认值,如果没有设置默认值,框架会抛出JS ERROR。 124 3. 在初始化\@Consume变量时,如果在Map中有该变量名/alias(别名)对应的\@Provide的变量,则和\@State/\@Link的流程类似,\@Consume变量会在Map中查找到对应的\@Provide变量进行保存,并把自己注册给\@Provide。 125 4. 从API version 20开始,在初始化\@Consume变量时,如果在Map中没有该变量名/alias(别名)对应的\@Provide的变量,而\@Consume的变量设置了默认值时,\@Consume变量会利用默认值创建一个临时的数据源,保证通知链路的连续性。 126 1272. 当\@Provide装饰的数据变化时: 128 1. 通过初始渲染的步骤可知,子组件\@Consume已把自己注册给父组件。父组件\@Provide变量变更后,会遍历更新所有依赖它的系统组件(elementid)和状态变量(\@Consume)。 129 2. 通知\@Consume更新后,子组件所有依赖\@Consume的系统组件(elementId)都会被通知更新。以此实现\@Provide对\@Consume状态数据同步。 130 1313. 当\@Consume装饰的数据变化时: 132 133 通过初始渲染的步骤可知,子组件\@Consume持有\@Provide的实例。在\@Consume更新后调用\@Provide的更新方法,将更新的数值同步回\@Provide,以此实现\@Consume向\@Provide的同步更新。 134 135 136 137## 限制条件 138 1391. \@Provide/\@Consume的参数key必须为string类型,否则编译期会报错。 140 141 ```ts 142 // 错误写法,编译报错 143 let change: number = 10; 144 @Provide(change) message: string = 'Hello'; 145 146 // 正确写法 147 let change: string = 'change'; 148 @Provide(change) message: string = 'Hello'; 149 ``` 150 1512. \@Consume装饰的变量不能在构造参数中传入初始化,否则编译期会报错。\@Consume仅能通过key来匹配对应的\@Provide变量或者从API version 20开始设置默认值进行初始化。 152 153 【反例】 154 155 ```ts 156 @Component 157 struct Child { 158 @Consume msg: string; 159 160 build() { 161 Text(this.msg) 162 } 163 } 164 165 @Entry 166 @Component 167 struct Parent { 168 @Provide message: string = 'Hello'; 169 170 build() { 171 Column() { 172 // 错误写法,不允许外部传入初始化 173 Child({msg: 'Hello'}) 174 } 175 } 176 } 177 ``` 178 179 【正例】 180 181 ```ts 182 @Component 183 struct Child { 184 @Consume num: number; 185 // 从API version 20开始,@Consume装饰的变量支持设置默认值 186 @Consume num1: number = 17; 187 188 build() { 189 Column() { 190 Text(`num的值: ${this.num}`) 191 Text(`num1的值:${this.num1}`) 192 } 193 } 194 } 195 196 @Entry 197 @Component 198 struct Parent { 199 @Provide num: number = 10; 200 201 build() { 202 Column() { 203 Text(`num的值: ${this.num}`) 204 Child() 205 } 206 } 207 } 208 ``` 209 2103. \@Provide的key重复定义时,框架会抛出运行时错误,提醒开发者重复定义key,如果开发者需要重复key,可以使用[allowoverride](#provide支持allowoverride参数)。 211 212 ```ts 213 // 错误写法,a重复定义 214 @Provide('a') count: number = 10; 215 @Provide('a') num: number = 10; 216 217 // 正确写法 218 @Provide('a') count: number = 10; 219 @Provide('b') num: number = 10; 220 ``` 221 2224. 在API version 20之前,初始化\@Consume变量时,如果开发者没有定义对应key的\@Provide变量,框架会抛出运行时错误,提示开发者初始化\@Consume变量失败,原因是无法找到其对应key的\@Provide变量。从API version 20开始,初始化\@Consume变量时,如果开发者没有定义对应key的\@Provide变量,同时没有设置默认值,框架会抛出运行时错误,提示开发者初始化\@Consume变量失败,原因是无法找到其对应key的\@Provide变量同时也没有设置默认值。 223 224 【反例】 225 226 ```ts 227 @Component 228 struct Child { 229 @Consume num: number; 230 231 build() { 232 Column() { 233 Text(`num的值: ${this.num}`) 234 } 235 } 236 } 237 238 @Entry 239 @Component 240 struct Parent { 241 // 错误写法,缺少@Provide 242 num: number = 10; 243 244 build() { 245 Column() { 246 Text(`num的值: ${this.num}`) 247 Child() 248 } 249 } 250 } 251 ``` 252 253 【正例】 254 255 ```ts 256 @Component 257 struct Child { 258 @Consume num: number; 259 // 正确写法 从API version 20开始,@Consume装饰的变量支持设置默认值 260 @Consume num_with_defaultValue: number = 6; 261 262 build() { 263 Column() { 264 Text(`num的值: ${this.num}`) 265 Text(`num_with_defaultValue的值:${this.num_with_defaultValue}`) 266 } 267 } 268 } 269 270 @Entry 271 @Component 272 struct Parent { 273 // 正确写法 274 @Provide num: number = 10; 275 276 build() { 277 Column() { 278 Text(`num的值: ${this.num}`) 279 Child() 280 } 281 } 282 } 283 ``` 284 2855. \@Provide与\@Consume不支持装饰Function类型的变量,框架会抛出运行时错误。 286 2876. 从API version 20开始,支持跨BuilderNode配对\@Provide/\@Consume。在BuilderNode上树时,\@Consume通过key匹配找到最近的\@Provide,两者类型需要一致,如果不一致,则会抛出运行时错误。 288需要注意类型不相等判断,包括类实例的判断,比如: 289```ts 290class A {} 291class B {} 292// 两个message都为object类型,但其构造函数不同,属于不同类型 293@Provide message: A = new A(); 294@Consume message: B = new B(); 295``` 296在非BuilderNode场景中,仍建议配对的\@Provide/\@Consume类型一致。虽然在运行时不会有强校验,但在\@Consume装饰的变量初始化时,会隐式转换成\@Provide装饰变量的类型。 297```ts 298import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI'; 299 300@Builder 301function buildText() { 302 Column() { 303 Child() 304 } 305} 306 307class TextNodeController extends NodeController { 308 private builderNode: BuilderNode<[]> | null = null; 309 310 constructor() { 311 super(); 312 } 313 314 makeNode(context: UIContext): FrameNode | null { 315 this.builderNode = new BuilderNode(context); 316 // 配置跨BuilderNode支持@Provide/@Consume 317 this.builderNode.build(wrapBuilder(buildText), undefined, 318 { enableProvideConsumeCrossing: true }); 319 // 将BuilderNode的根节点挂载到NodeContainer 320 return this.builderNode.getFrameNode(); 321 } 322} 323 324@Entry 325@Component 326struct Index { 327 @Provide message: string = 'hello'; 328 controller: TextNodeController = new TextNodeController(); 329 330 build() { 331 Column() { 332 NodeContainer(this.controller) 333 .width('100%') 334 .height(100) 335 } 336 .width('100%') 337 .height('100%') 338 } 339} 340 341 342@Component 343struct Child { 344 // Child通过BuilderNode上树后,@Consume和Index中的@Provide建立连接时发现类型不一致,抛出运行时错误 345 @Consume message: number = 0; 346 347 build() { 348 Column() { 349 Text(`@Consume ${this.message}`) 350 } 351 } 352} 353``` 354 355## 使用场景 356 357以下示例是@Provide变量与后代组件中@Consume变量进行双向同步的场景。当分别点击ToDo和ToDoItem组件内的Button时,count的更改会双向同步在ToDo和ToDoItem中。 358 359```ts 360@Component 361struct ToDoItem { 362 // @Consume装饰的变量通过相同的属性名绑定其祖先组件ToDo内的@Provide装饰的变量 363 @Consume count: number; 364 365 build() { 366 Column() { 367 Text(`count(${this.count})`) 368 Button(`count(${this.count}), count + 1`) 369 .onClick(() => this.count += 1) 370 } 371 .width('50%') 372 } 373} 374 375@Component 376struct ToDoList { 377 build() { 378 Row({ space: 5 }) { 379 ToDoItem() 380 ToDoItem() 381 } 382 } 383} 384 385@Component 386struct ToDoDemo { 387 build() { 388 ToDoList() 389 } 390} 391 392@Entry 393@Component 394struct ToDo { 395 // @Provide装饰的变量count由入口组件ToDo提供其后代组件 396 @Provide count: number = 0; 397 398 build() { 399 Column() { 400 Button(`count(${this.count}), count + 1`) 401 .onClick(() => this.count += 1) 402 ToDoDemo() 403 } 404 } 405} 406``` 407### 装饰Map类型变量 408 409> **说明:** 410> 411> 从API version 11开始,\@Provide,\@Consume支持Map类型。 412 413以下示例中,message类型为Map\<number, string\>,点击Button改变message的值,视图会随之刷新。 414 415```ts 416@Component 417struct Child { 418 @Consume message: Map<number, string> 419 420 build() { 421 Column() { 422 ForEach(Array.from(this.message.entries()), (item: [number, string]) => { 423 Text(`${item[0]}`).fontSize(30) 424 Text(`${item[1]}`).fontSize(30) 425 Divider() 426 }) 427 Button('Consume init Map').onClick(() => { 428 this.message = new Map([[0, 'a'], [1, 'b'], [3, 'c']]) 429 }) 430 Button('Consume set new one').onClick(() => { 431 this.message.set(4, 'd') 432 }) 433 Button('Consume clear').onClick(() => { 434 this.message.clear() 435 }) 436 Button('Consume replace the first item').onClick(() => { 437 this.message.set(0, 'aa') 438 }) 439 Button('Consume delete the first item').onClick(() => { 440 this.message.delete(0) 441 }) 442 } 443 } 444} 445 446 447@Entry 448@Component 449struct MapSample { 450 @Provide message: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]) 451 452 build() { 453 Row() { 454 Column() { 455 Button('Provide init Map').onClick(() => { 456 this.message = new Map([[0, 'a'], [1, 'b'], [3, 'c'], [4, 'd']]) 457 }) 458 Child() 459 } 460 .width('100%') 461 } 462 .height('100%') 463 } 464} 465``` 466 467### 装饰Set类型变量 468 469> **说明:** 470> 471> 从API version 11开始,\@Provide,\@Consume支持Set类型。 472 473以下示例中,message类型为Set\<number\>,点击Button改变message的值,视图会随之刷新。 474 475```ts 476@Component 477struct Child { 478 @Consume message: Set<number> 479 480 build() { 481 Column() { 482 ForEach(Array.from(this.message.entries()), (item: [number, number]) => { 483 Text(`${item[0]}`).fontSize(30) 484 Divider() 485 }) 486 Button('Consume init set').onClick(() => { 487 this.message = new Set([0, 1, 2, 3, 4]) 488 }) 489 Button('Consume set new one').onClick(() => { 490 this.message.add(5) 491 }) 492 Button('Consume clear').onClick(() => { 493 this.message.clear() 494 }) 495 Button('Consume delete the first one').onClick(() => { 496 this.message.delete(0) 497 }) 498 } 499 .width('100%') 500 } 501} 502 503 504@Entry 505@Component 506struct SetSample { 507 @Provide message: Set<number> = new Set([0, 1, 2, 3, 4]) 508 509 build() { 510 Row() { 511 Column() { 512 Button('Provide init set').onClick(() => { 513 this.message = new Set([0, 1, 2, 3, 4, 5]) 514 }) 515 Child() 516 } 517 .width('100%') 518 } 519 .height('100%') 520 } 521} 522``` 523 524### 装饰Date类型变量 525 526以下示例中,selectedDate类型为Date,点击Button改变selectedDate的值,视图会随之刷新。 527 528```ts 529@Component 530struct Child { 531 @Consume selectedDate: Date; 532 533 build() { 534 Column() { 535 Button(`child increase the day by 1`) 536 .onClick(() => { 537 this.selectedDate.setDate(this.selectedDate.getDate() + 1) 538 }) 539 Button('child update the new date') 540 .margin(10) 541 .onClick(() => { 542 this.selectedDate = new Date('2023-09-09') 543 }) 544 DatePicker({ 545 start: new Date('1970-1-1'), 546 end: new Date('2100-1-1'), 547 selected: this.selectedDate 548 }) 549 } 550 } 551} 552 553@Entry 554@Component 555struct Parent { 556 @Provide selectedDate: Date = new Date('2021-08-08') 557 558 build() { 559 Column() { 560 Button('parent increase the day by 1') 561 .margin(10) 562 .onClick(() => { 563 this.selectedDate.setDate(this.selectedDate.getDate() + 1) 564 }) 565 Button('parent update the new date') 566 .margin(10) 567 .onClick(() => { 568 this.selectedDate = new Date('2023-07-07') 569 }) 570 DatePicker({ 571 start: new Date('1970-1-1'), 572 end: new Date('2100-1-1'), 573 selected: this.selectedDate 574 }) 575 Child() 576 } 577 } 578} 579``` 580 581### Provide和Consume支持联合类型实例 582 583@Provide和@Consume支持联合类型和undefined和null。以下示例中,count类型为string | undefined,当点击父组件Parent中的Button改变count的属性或者类型时,Child中也会对应刷新。 584 585```ts 586@Component 587struct Child { 588 // @Consume装饰的变量通过相同的属性名绑定其祖先组件Ancestors内的@Provide装饰的变量 589 @Consume count: string | undefined; 590 591 build() { 592 Column() { 593 Text(`count(${this.count})`) 594 Button(`count(${this.count}), Child`) 595 .onClick(() => this.count = 'Ancestors') 596 } 597 .width('50%') 598 } 599} 600 601@Component 602struct Parent { 603 build() { 604 Row({ space: 5 }) { 605 Child() 606 } 607 } 608} 609 610@Entry 611@Component 612struct Ancestors { 613 // @Provide装饰的联合类型count由入口组件Ancestors提供其后代组件 614 @Provide count: string | undefined = 'Child'; 615 616 build() { 617 Column() { 618 Button(`count(${this.count}), Child`) 619 .onClick(() => this.count = undefined) 620 Parent() 621 } 622 } 623} 624``` 625 626### \@Provide支持allowOverride参数 627 628allowOverride:\@Provide重写选项。 629 630> **说明:** 631> 632> 从API version 11开始使用。 633 634| 名称 | 类型 | 必填 | 说明 | 635| ------ | ------ | ---- | ------------------------------------------------------------ | 636| allowOverride | string | 否 | 是否允许@Provide重写。允许在同一组件树下通过allowOverride重写同名的@Provide。如果开发者未写allowOverride,定义同名的@Provide,运行时会报错。 | 637 638```ts 639@Component 640struct MyComponent { 641 @Provide({allowOverride : 'reviewVotes'}) reviewVotes: number = 10; 642} 643``` 644 645完整示例如下: 646 647```ts 648@Component 649struct GrandSon { 650 // @Consume装饰的变量通过相同的属性名绑定其祖先内的@Provide装饰的变量 651 @Consume('reviewVotes') reviewVotes: number; 652 653 build() { 654 Column() { 655 Text(`reviewVotes(${this.reviewVotes})`) // Text显示10 656 Button(`reviewVotes(${this.reviewVotes}), give +1`) 657 .onClick(() => this.reviewVotes += 1) 658 } 659 .width('50%') 660 } 661} 662 663@Component 664struct Child { 665 @Provide({ allowOverride: 'reviewVotes' }) reviewVotes: number = 10; 666 667 build() { 668 Row({ space: 5 }) { 669 GrandSon() 670 } 671 } 672} 673 674@Component 675struct Parent { 676 @Provide({ allowOverride: 'reviewVotes' }) reviewVotes: number = 20; 677 678 build() { 679 Child() 680 } 681} 682 683@Entry 684@Component 685struct GrandParent { 686 @Provide('reviewVotes') reviewVotes: number = 40; 687 688 build() { 689 Column() { 690 Button(`reviewVotes(${this.reviewVotes}), give +1`) 691 .onClick(() => this.reviewVotes += 1) 692 Parent() 693 } 694 } 695} 696``` 697 698在上面的示例中: 699- GrandParent声明了@Provide('reviewVotes') reviewVotes: number = 40。 700- Parent是GrandParent的子组件,声明@Provide为allowOverride,重写父组件GrandParent的@Provide('reviewVotes') reviewVotes: number = 40。如果不设置allowOverride,则会抛出运行时报错,提示@Provide重复定义。Child同理。 701- GrandSon在初始化@Consume的时候,@Consume装饰的变量通过相同的属性名绑定其最近的祖先的@Provide装饰的变量。 702- GrandSon查找到相同属性名的@Provide在祖先Child中,所以@Consume('reviewVotes') reviewVotes: number初始化数值为10。如果Child中没有定义与@Consume同名的@Provide,则继续向上寻找Parent中的同名@Provide值为20,以此类推。 703- 如果查找到根节点还没有找到key对应的@Provide,则会报初始化@Consume找不到@Provide的报错。 704 705### \@Consume装饰的变量支持设置默认值 706 707> **说明:** 708> 709> 从API version 20开始,\@Consume装饰的变量支持设置默认值。 710 711```ts 712@Component 713struct MyComponent { 714 @Consume('withDefault') defaultValue: number = 10; 715} 716``` 717 718完整示例如下: 719 720```ts 721@Entry 722@Component 723struct Parent { 724 @Provide('firstKey') provideOne: string | undefined = undefined; 725 @Provide('secondKey') provideTwo: string = 'the second provider'; 726 727 build(){ 728 Column(){ 729 Row(){ 730 Column() { 731 Text(`${this.provideOne}`) 732 Text(`${this.provideTwo}`) 733 } 734 735 Column(){ 736 // 点击change provideOne按钮,provideOne和子组件中的textOne属性会同时变化 737 Button('change provideOne') 738 .onClick(() => { 739 this.provideOne = undefined; 740 }) 741 // 点击change provideTwo按钮,provideTwo和子组件中的textTwo属性会同时变化 742 Button('change provideTwo') 743 .onClick(() => { 744 this.provideTwo = 'the next provider'; 745 }) 746 } 747 } 748 749 Row(){ 750 Column() { 751 Child() 752 } 753 } 754 } 755 } 756} 757 758@Component 759struct Child { 760 // @Consume装饰的变量通过相同的别名绑定其祖先内的@Provide装饰的变量,同时设置默认值 761 @Consume('firstKey') textOne: string | undefined = 'child'; 762 // @Consume装饰的变量通过相同的别名绑定其祖先内的@Provide装饰的变量,没有设置默认值 763 @Consume('secondKey') textTwo: string; 764 // @Consume装饰的变量在祖先内没有匹配成功的@Provide装饰的变量,但设置了默认值 765 @Consume('thirdKey') textThree: string = 'defaultValue'; 766 767 build(){ 768 Column() { 769 Text(`${this.textOne}`) 770 Text(`${this.textTwo}`) 771 Text(`${this.textThree}`) 772 // 点击change textOne按钮,textOne和父组件的provideOne会同时变化 773 Button('change textOne') 774 .onClick(() => { 775 this.textOne = 'not undefined'; 776 }) 777 // 点击change textTwo按钮,textTwo和父组件的provideTwo会同时变化 778 Button('change textTwo') 779 .onClick(() => { 780 this.textTwo = 'change textTwo'; 781 }) 782 } 783 } 784} 785``` 786 787在上面的示例中: 788- Parent声明了@Provide('firstKey') provideOne: string | undefined = undefined 与 @Provide('secondKey') provideTwo: string = 'the second provider'。 789- Child声明了@Consume('firstKey') textOne: string | undefined = 'child',@Consume('secondKey') textTwo: string 与 @Consume('thirdKey') textThree: string = 'defaultValue'。 790- Child是Parent的子组件,Child在初始化@Consume装饰的三个属性时,textOne根据'firstKey'别名绑定Parent中的provideOne属性,provideOne的值会覆盖textOne的默认值,所以textOne初始化的值为undefined;textTwo根据'secondKey'别名绑定Parent中的providedTwo属性,textTwo初始化的值为'the second provider';textThree在祖先组件中不存在匹配结果,如果@Consume没有设置默认值,则会抛出运行时错误,示例中textThree有默认值'defaultValue',所以textThree初始化的值为'defaultValue'。 791- @Consume装饰的属性设置的默认值仅在祖先组件没有匹配结果时才生效,有匹配结果时无影响。 792 793### \@Consume在跨BuilderNode场景下和\@Provide建立双向同步 794BuilderNode支持\@Provide/\@Consume,需注意: 7951. 在BuilderNode子树中定义的\@Consume需要设置默认值,或者在子树中已存在配对的\@Provide,否则会发生运行时报错。 7962. BuilderNode上树后,设置默认值的\@Consume会向上查找\@Provide,根据key的匹配规则找到最近的\@Provide后,会和\@Provide建立双向同步关系。如果找不到配对的\@Provide,则\@Consume仍使用默认值。 7973. 建立双向同步的关系后,如果\@Provide装饰变量的值和\@Consume的默认值不同,则会回调\@Consume的\@Watch方法,以及与\@Consume有同步关系的变量的\@Watch方法,例如\@Consume通知与其双向同步的\@Link触发\@Watch方法。 7984. BuilderNode下树后,\@Consume会再次试图查找对应的\@Provide,如果发现下树后无法再找到之前配对的\@Provide,则断开和\@Provide的双向同步关系,\@Consume装饰的变量恢复成默认值。 7995. \@Consume断开和\@Provide的连接,恢复成默认值时,会判断\@Consume装饰变量的值从和\@Provide变为\@Consume的默认值是否有变化,如果有变化,则会回调\@Consume以及与其有同步关系变量的\@Watch方法。 800 801在下面的例子中: 8021. 点击`add Child`: 803 - 构建BuilderNode下的子节点`Child`,`Child`中\@Consume未找到\@Provide,使用本地默认值`default value`初始化。 804 - BuilderNode上树时,`Child`中\@Consume向上找到最近的`Index`中的\@Provide,将\@Consume从默认值更新为\@Provide的值,并回调\@Consume的\@Watch方法。 8052. \@Provide和\@Consume配对后,建立双向同步关系。点击```Text(`@Provide: ${this.message}`)```和```Text(`@Consume ${this.message}`)```,\@Provide和\@Consume绑定的Text组件刷新,并回调\@Provide和\@Consume的\@Watch方法。 8063. 点击`remove Child`, BuilderNode子节点下树,`Child`中的\@Consume和`Index`中的\@Provide断开连接,`Child`中的\@Consume恢复成默认值,并回调\@Consume的\@Watch方法。 8074. 点击`dispose Child`,释放BuilderNode下子节点,BuilderNode子节点`Child`销毁,执行aboutToDisappear。 808 809```ts 810import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI'; 811 812@Builder 813function buildText() { 814 Column() { 815 Child() 816 } 817} 818 819class TextNodeController extends NodeController { 820 private rootNode: FrameNode | null = null; 821 private uiContext: UIContext | null = null; 822 private builderNode: BuilderNode<[]> | null = null; 823 824 constructor() { 825 super(); 826 } 827 828 makeNode(context: UIContext): FrameNode | null { 829 this.rootNode = new FrameNode(context); 830 this.uiContext = context; 831 // 将rootNode节点挂载在NodeContainer下 832 return this.rootNode; 833 } 834 835 addBuilderNode(): void { 836 if (this.builderNode === null && this.uiContext && this.rootNode) { 837 this.builderNode = new BuilderNode(this.uiContext); 838 // 配置跨BuilderNode支持@Provide/@Consume 839 this.builderNode.build(wrapBuilder(buildText), undefined, 840 { enableProvideConsumeCrossing: true }); 841 // 将BuilderNode的根节点挂载到rootNode节点下 842 this.rootNode.appendChild(this.builderNode.getFrameNode()); 843 } 844 } 845 846 removeBuilderNode(): void { 847 if (this.rootNode && this.builderNode) { 848 // 从rootNode节点下的BuildNode节点移除 849 this.rootNode.removeChild(this.builderNode.getFrameNode()); 850 } 851 } 852 853 disposeNode(): void { 854 if (this.rootNode && this.builderNode) { 855 // 立即释放当前BuilderNode 856 this.builderNode.dispose(); 857 } 858 } 859} 860 861@Entry 862@Component 863struct Index { 864 @Provide @Watch('onChange') message: string = 'hello'; 865 controller: TextNodeController = new TextNodeController(); 866 867 onChange() { 868 console.info(`Index Provide change ${this.message}`); 869 } 870 871 build() { 872 Column() { 873 Text(`@Provide: ${this.message}`) 874 .fontSize(20) 875 .onClick(() => { 876 this.message += ' Provide'; 877 }) 878 879 // 执行BuilderNode的build方法,构造Child自定义组件 880 // 并将BuilderNode挂载在NodeContainer下 881 // Child中@Consume可以和当前Index中的@Provide配对 882 // @Consume装饰的变量message从default value变为hello,并回调@Consume的@Watch方法 883 Button('add Child') 884 .onClick(() => { 885 this.controller.addBuilderNode(); 886 }) 887 // 将BuilderNode下的节点从NodeContainer上移除 888 // @Consume修饰的变量message从和@Provide配对的值变为default value,并回调@Consume的@Watch方法 889 Button('remove Child') 890 .onClick(() => { 891 this.controller.removeBuilderNode(); 892 }) 893 894 // 立即释放当前BuilderNode,BuilderNode下节点销毁,Child组件执行aboutToDisappear 895 Button('dispose Child') 896 .onClick(() => { 897 this.controller.disposeNode(); 898 }) 899 NodeContainer(this.controller) 900 .width('100%') 901 .height(100) 902 .backgroundColor(Color.Pink) 903 } 904 .width('100%') 905 .height('100%') 906 } 907} 908 909 910@Component 911struct Child { 912 @Consume @Watch('onChange') message: string = 'default value'; 913 914 onChange() { 915 console.info(`Child Consume change ${this.message}`); 916 } 917 918 aboutToDisappear(): void { 919 console.info(`Child aboutToDisappear`); 920 } 921 922 build() { 923 Column() { 924 Text(`@Consume ${this.message}`) 925 .fontSize(20) 926 .onClick(() => { 927 this.message += ' Consume'; 928 }) 929 } 930 } 931} 932``` 933 934## 常见问题 935 936### \@BuilderParam尾随闭包情况下\@Provide未定义错误 937 938在此[尾随闭包](arkts-builderparam.md#尾随闭包初始化组件)场景下,CustomWidget执行this.builder()创建子组件CustomWidgetChild时,this指向的是HomePage。因此找不到CustomWidget的\@Provide变量,所以下面示例会报找不到\@Provide错误,和\@BuilderParam连用的时候要谨慎this的指向。 939 940错误示例: 941 942```ts 943class Tmp { 944 a: string = '' 945} 946 947@Entry 948@Component 949struct HomePage { 950 // 错误点1:HomePage未声明@Provide 951 @Builder 952 builder2($$: Tmp) { 953 Text(`${$$.a}测试`) 954 } 955 956 build() { 957 Column() { 958 // 错误点2:使用尾随闭包的形式将创建CustomWidgetChild的函数传递给CustomWidget,此时尾随闭包中this指向HomePage 959 CustomWidget() { 960 CustomWidgetChild({ builder: this.builder2 }) 961 } 962 } 963 } 964} 965 966@Component 967struct CustomWidget { 968 // 错误点3:@Provide变量声明在CustomWidget中,仅有CustomWidget自身及其子组件能够消费 969 @Provide('a') a: string = 'abc'; 970 @BuilderParam 971 builder: () => void; 972 973 build() { 974 Column() { 975 Button('你好').onClick(() => { 976 if (this.a == 'ddd') { 977 this.a = 'abc'; 978 } 979 else { 980 this.a = 'ddd'; 981 } 982 983 }) 984 this.builder() 985 } 986 } 987} 988 989@Component 990struct CustomWidgetChild { 991 // 错误点4:尝试消费CustomWidget的@Provide('a'),但实际上CustomWidgetChild的父组件为HomePage,无法找到对应的@Provide 992 @Consume('a') a: string; 993 @BuilderParam 994 builder: ($$: Tmp) => void; 995 996 build() { 997 Column() { 998 this.builder({ a: this.a }) 999 } 1000 } 1001} 1002``` 1003 1004正确示例: 1005 1006```ts 1007class Tmp { 1008 name: string = '' 1009} 1010 1011@Entry 1012@Component 1013struct HomePage { 1014 // 修正点1:将@Provide声明在Entry组件(根作用域),确保子组件能正确消费 1015 @Provide('name') name: string = 'abc'; 1016 1017 @Builder 1018 builder2($$: Tmp) { 1019 Text(`${$$.name}测试`) 1020 } 1021 1022 build() { 1023 Column() { 1024 Button('你好').onClick(() => { 1025 if (this.name == 'ddd') { 1026 this.name = 'abc'; 1027 } else { 1028 this.name = 'ddd'; 1029 } 1030 }) 1031 // 修正点2:CustomWidget不再声明@Provide,仅作为容器传递builder 1032 CustomWidget() { 1033 CustomWidgetChild({ builder: this.builder2 }) 1034 } 1035 } 1036 } 1037} 1038 1039@Component 1040struct CustomWidget { 1041 @BuilderParam 1042 builder: () => void; 1043 1044 build() { 1045 this.builder() 1046 } 1047} 1048 1049@Component 1050struct CustomWidgetChild { 1051 // 修正点3:@Consume从根作用域(HomePage)获取@Provide('name'),作用域正确 1052 @Consume('name') name: string; 1053 @BuilderParam 1054 builder: ($$: Tmp) => void; 1055 1056 build() { 1057 Column() { 1058 this.builder({ name: this.name }) 1059 } 1060 } 1061} 1062``` 1063 1064### 使用a.b(this.object)形式调用,不会触发UI刷新 1065 1066在build方法内,当@Provide与@Consume装饰的变量是Object类型、且通过a.b(this.object)形式调用时,b方法内传入的是this.object的原始对象,修改其属性,无法触发UI刷新。如下例中,通过静态方法或者使用this调用组件内部方法,修改组件中的this.dog.age与this.dog.name时,UI不会刷新。 1067 1068【反例】 1069 1070```ts 1071class Animal { 1072 name:string; 1073 type:string; 1074 age: number; 1075 1076 constructor(name:string, type:string, age:number) { 1077 this.name = name; 1078 this.type = type; 1079 this.age = age; 1080 } 1081 1082 static changeName(animal:Animal) { 1083 animal.name = 'Jack'; 1084 } 1085 static changeAge(animal:Animal) { 1086 animal.age += 1; 1087 } 1088} 1089 1090@Entry 1091@Component 1092struct Zoo { 1093 @Provide dog:Animal = new Animal('WangCai', 'dog', 2); 1094 1095 changeZooDogAge(animal:Animal) { 1096 animal.age += 2; 1097 } 1098 1099 build() { 1100 Column({ space:10 }) { 1101 Text(`Zoo: This is a ${this.dog.age}-year-old ${this.dog.type} named ${this.dog.name}.`) 1102 .fontColor(Color.Red) 1103 .fontSize(30) 1104 Button('changeAge') 1105 .onClick(()=>{ 1106 // 通过静态方法调用,无法触发UI刷新 1107 Animal.changeAge(this.dog); 1108 }) 1109 Button('changeZooDogAge') 1110 .onClick(()=>{ 1111 // 使用this通过自定义组件内部方法调用,无法触发UI刷新 1112 this.changeZooDogAge(this.dog); 1113 }) 1114 ZooChild() 1115 } 1116 } 1117} 1118 1119@Component 1120struct ZooChild { 1121 1122 build() { 1123 Column({ space:10 }) { 1124 Text(`ZooChild`) 1125 .fontColor(Color.Blue) 1126 .fontSize(30) 1127 ZooGrandChild() 1128 } 1129 } 1130} 1131 1132@Component 1133struct ZooGrandChild { 1134 @Consume dog:Animal; 1135 1136 changeZooGrandChildName(animal:Animal) { 1137 animal.name = 'Marry'; 1138 } 1139 1140 build() { 1141 Column({ space:10 }) { 1142 Text(`ZooGrandChild: This is a ${this.dog.age}-year-old ${this.dog.type} named ${this.dog.name}.`) 1143 .fontColor(Color.Yellow) 1144 .fontSize(30) 1145 Button('changeName') 1146 .onClick(()=>{ 1147 // 通过静态方法调用,无法触发UI刷新 1148 Animal.changeName(this.dog); 1149 }) 1150 Button('changeZooGrandChildName') 1151 .onClick(()=>{ 1152 // 使用this通过自定义组件内部方法调用,无法触发UI刷新 1153 this.changeZooGrandChildName(this.dog); 1154 }) 1155 } 1156 } 1157} 1158``` 1159 1160可以通过如下先赋值、再调用新赋值的变量的方式为this.dog保留Proxy代理,实现UI刷新。 1161 1162【正例】 1163 1164```ts 1165class Animal { 1166 name:string; 1167 type:string; 1168 age: number; 1169 1170 constructor(name:string, type:string, age:number) { 1171 this.name = name; 1172 this.type = type; 1173 this.age = age; 1174 } 1175 1176 static changeName(animal:Animal) { 1177 animal.name = 'Jack'; 1178 } 1179 static changeAge(animal:Animal) { 1180 animal.age += 1; 1181 } 1182} 1183 1184@Entry 1185@Component 1186struct Zoo { 1187 @Provide dog:Animal = new Animal('WangCai', 'dog', 2); 1188 1189 changeZooDogAge(animal:Animal) { 1190 animal.age += 2; 1191 } 1192 1193 build() { 1194 Column({ space:10 }) { 1195 Text(`Zoo: This is a ${this.dog.age}-year-old ${this.dog.type} named ${this.dog.name}.`) 1196 .fontColor(Color.Red) 1197 .fontSize(30) 1198 Button('changeAge') 1199 .onClick(()=>{ 1200 // 通过赋值给临时变量保留Proxy代理 1201 let newDog = this.dog; 1202 Animal.changeAge(newDog); 1203 }) 1204 Button('changeZooDogAge') 1205 .onClick(()=>{ 1206 // 通过赋值给临时变量保留Proxy代理 1207 let newDog = this.dog; 1208 this.changeZooDogAge(newDog); 1209 }) 1210 ZooChild() 1211 } 1212 } 1213} 1214 1215@Component 1216struct ZooChild { 1217 1218 build() { 1219 Column({ space:10 }) { 1220 Text(`ZooChild.`) 1221 .fontColor(Color.Blue) 1222 .fontSize(30) 1223 ZooGrandChild() 1224 } 1225 } 1226} 1227 1228@Component 1229struct ZooGrandChild { 1230 @Consume dog:Animal; 1231 1232 changeZooGrandChildName(animal:Animal) { 1233 animal.name = 'Marry'; 1234 } 1235 1236 build() { 1237 Column({ space:10 }) { 1238 Text(`ZooGrandChild: This is a ${this.dog.age}-year-old ${this.dog.type} named ${this.dog.name}.`) 1239 .fontColor(Color.Yellow) 1240 .fontSize(30) 1241 Button('changeName') 1242 .onClick(()=>{ 1243 // 通过赋值给临时变量保留Proxy代理 1244 let newDog = this.dog; 1245 Animal.changeName(newDog); 1246 }) 1247 Button('changeZooGrandChildName') 1248 .onClick(()=>{ 1249 // 通过赋值给临时变量保留Proxy代理 1250 let newDog = this.dog; 1251 this.changeZooGrandChildName(newDog); 1252 }) 1253 } 1254 } 1255} 1256``` 1257