1# \@Provide装饰器和\@Consume装饰器:与后代组件双向同步 2 3 4\@Provide和\@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,\@Provide和\@Consume摆脱参数传递机制的束缚,实现跨层级传递。 5 6 7其中\@Provide装饰的变量是在祖先组件中,可以理解为被“提供”给后代的状态变量。\@Consume装饰的变量是在后代组件中,去“消费(绑定)”祖先组件提供的变量。 8 9\@Provide/\@Consume是跨组件层级的双向同步。在阅读\@Provide和\@Consume文档前,建议开发者对UI范式基本语法和自定义组件有基本的了解。建议提前阅读:[基本语法概述](./arkts-basic-syntax-overview.md),[声明式UI描述](./arkts-declarative-ui-description.md),[自定义组件-创建自定义组件](./arkts-create-custom-components.md)。 10 11> **说明:** 12> 13> 从API version 9开始,这两个装饰器支持在ArkTS卡片中使用。 14> 15> 从API version 11开始,这两个装饰器支持在原子化服务中使用。 16 17## 概述 18 19\@Provide/\@Consume装饰的状态变量有以下特性: 20 21- \@Provide装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。由此可见,\@Provide的方便之处在于,开发者不需要多次在组件之间传递变量。 22 23- 后代通过使用\@Consume去获取\@Provide提供的变量,建立在\@Provide和\@Consume之间的双向数据同步,与\@State/\@Link不同的是,前者可以在多层级的父子组件之间传递。 24 25- \@Provide和\@Consume可以通过相同的变量名或者相同的变量别名绑定,建议类型相同,否则会发生类型隐式转换,从而导致应用行为异常。 26 27 28```ts 29// 通过相同的变量名绑定 30@Provide age: number = 0; 31@Consume age: number; 32 33// 通过相同的变量别名绑定 34@Provide('a') id: number = 0; 35@Consume('a') age: number; 36``` 37 38 39\@Provide和\@Consume通过相同的变量名或者相同的变量别名绑定时,\@Provide装饰的变量和\@Consume装饰的变量是一对多的关系。不允许在同一个自定义组件内,包括其子组件中声明多个同名或者同别名的\@Provide装饰的变量,@Provide的属性名或别名需要唯一且确定,如果声明多个同名或者同别名的@Provide装饰的变量,会发生运行时报错。 40 41 42## 装饰器说明 43 44\@State的规则同样适用于\@Provide,差异为\@Provide还作为多层后代的同步源。 45 46| \@Provide变量装饰器 | 说明 | 47| -------------- | ---------------------------------------- | 48| 装饰器参数 | 别名:常量字符串,可选。<br/>如果指定了别名,则通过别名来绑定变量;如果未指定别名,则通过变量名绑定变量。 | 49| 同步类型 | 双向同步。<br/>从\@Provide变量到所有\@Consume变量以及相反的方向的数据同步。双向同步的操作与\@State和\@Link的组合相同。 | 50| 允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>支持Date类型。<br/>API11及以上支持Map、Set类型。<br/>支持ArkUI框架定义的联合类型Length、ResourceStr、ResourceColor类型。<br/>必须指定类型。<br/>\@Provide变量的\@Consume变量的类型必须相同。<br/>支持类型的场景请参考[观察变化](#观察变化)。<br/>不支持any。<br/>API11及以上支持上述支持类型的联合类型,比如string \| number, string \| undefined 或者 ClassA \| null,示例见[@Provide_and_Consume支持联合类型实例](#provide_and_consume支持联合类型实例)。 <br/>**注意**<br/>当使用undefined和null的时候,建议显式指定类型,遵循TypeScript类型校验,比如:`@Provide a : string \| undefined = undefined`是推荐的,不推荐`@Provide a: string = undefined`。 51| 被装饰变量的初始值 | 必须指定。 | 52| 支持allowOverride参数 | 允许重写,只要声明了allowOverride,则别名和属性名都可以被Override。示例见[\@Provide支持allowOverride参数](#provide支持allowoverride参数)。 | 53 54| \@Consume变量装饰器 | 说明 | 55| -------------- | ---------------------------------------- | 56| 装饰器参数 | 别名:常量字符串,可选。<br/>如果提供了别名,则必须有\@Provide的变量和其有相同的别名才可以匹配成功;否则,则需要变量名相同才能匹配成功。 | 57| 同步类型 | 双向:从\@Provide变量(具体请参见\@Provide)到所有\@Consume变量,以及相反的方向。双向同步操作与\@State和\@Link的组合相同。 | 58| 允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>支持Date类型。<br/>支持ArkUI框架定义的联合类型Length、ResourceStr、ResourceColor类型。必须指定类型。<br/>\@Provide变量和\@Consume变量的类型必须相同。<br/>\@Consume装饰的变量,在其父组件或者祖先组件上,必须有对应的属性和别名的\@Provide装饰的变量。<br/>支持类型的场景请参考[观察变化](#观察变化)。<br/>不支持any。<br/>API11及以上支持上述支持类型的联合类型,比如string \| number, string \| undefined 或者 ClassA \| null,示例见[@Provide_and_Consume支持联合类型实例](#provide_and_consume支持联合类型实例)。 <br/>**注意**<br/>当使用undefined和null的时候,建议显式指定类型,遵循TypeScript类型校验,比如:`@Consume a : string \| undefined`。 59| 被装饰变量的初始值 | 无,禁止本地初始化。 | 60 61 62## 变量的传递/访问规则说明 63 64 65| \@Provide传递/访问 | 说明 | 66| -------------- | ---------------------------------------- | 67| 从父组件初始化和更新 | 可选,允许父组件中常规变量(常规变量对@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。 | 68| 用于初始化子组件 | 允许,可用于初始化\@State、\@Link、\@Prop、\@Provide。 | 69| 和父组件同步 | 否。 | 70| 和后代组件同步 | 和\@Consume双向同步。 | 71| 是否支持组件外访问 | 私有,仅可以在所属组件内访问。 | 72 73 74 **图1** \@Provide初始化规则图示 75 76 77 78 79 80| \@Consume传递/访问 | 说明 | 81| -------------- | ---------------------------------------- | 82| 从父组件初始化和更新 | 禁止。通过相同的变量名和alias(别名)从\@Provide初始化。 | 83| 用于初始化子组件 | 允许,可用于初始化\@State、\@Link、\@Prop、\@Provide。 | 84| 和祖先组件同步 | 和\@Provide双向同步。 | 85| 是否支持组件外访问 | 私有,仅可以在所属组件内访问 | 86 87 88 **图2** \@Consume初始化规则图示 89 90 91 92 93 94## 观察变化和行为表现 95 96 97### 观察变化 98 99- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。 100 101- 当装饰的数据类型为class或者Object的时候,可以观察到赋值和属性赋值的变化(属性为Object.keys(observedObject)返回的所有属性)。 102 103- 当装饰的对象是array的时候,可以观察到数组的添加、删除、更新数组单元。 104 105- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性。 106 107```ts 108@Component 109struct Child { 110 @Consume selectedDate: Date; 111 112 build() { 113 Column() { 114 Button(`child increase the day by 1`) 115 .onClick(() => { 116 this.selectedDate.setDate(this.selectedDate.getDate() + 1) 117 }) 118 Button('child update the new date') 119 .margin(10) 120 .onClick(() => { 121 this.selectedDate = new Date('2023-09-09') 122 }) 123 DatePicker({ 124 start: new Date('1970-1-1'), 125 end: new Date('2100-1-1'), 126 selected: this.selectedDate 127 }) 128 } 129 } 130} 131 132@Entry 133@Component 134struct Parent { 135 @Provide selectedDate: Date = new Date('2021-08-08') 136 137 build() { 138 Column() { 139 Button('parent increase the day by 1') 140 .margin(10) 141 .onClick(() => { 142 this.selectedDate.setDate(this.selectedDate.getDate() + 1) 143 }) 144 Button('parent update the new date') 145 .margin(10) 146 .onClick(() => { 147 this.selectedDate = new Date('2023-07-07') 148 }) 149 DatePicker({ 150 start: new Date('1970-1-1'), 151 end: new Date('2100-1-1'), 152 selected: this.selectedDate 153 }) 154 Child() 155 } 156 } 157} 158``` 159 160- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口`set`, `clear`, `delete` 更新Map的值。详见[装饰Map类型变量](#装饰map类型变量)。 161 162- 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口`add`, `clear`, `delete` 更新Set的值。详见[装饰Set类型变量](#装饰set类型变量)。 163 164### 框架行为 165 1661. 初始渲染: 167 1. \@Provide装饰的变量会以map的形式,传递给当前\@Provide所属组件的所有子组件; 168 2. 子组件中如果使用\@Consume变量,则会在map中查找是否有该变量名/alias(别名)对应的\@Provide的变量,如果查找不到,框架会抛出JS ERROR; 169 3. 在初始化\@Consume变量时,和\@State/\@Link的流程类似,\@Consume变量会在map中查找到对应的\@Provide变量进行保存,并把自己注册给\@Provide。 170 1712. 当\@Provide装饰的数据变化时: 172 1. 通过初始渲染的步骤可知,子组件\@Consume已把自己注册给父组件。父组件\@Provide变量变更后,会遍历更新所有依赖它的系统组件(elementid)和状态变量(\@Consume); 173 2. 通知\@Consume更新后,子组件所有依赖\@Consume的系统组件(elementId)都会被通知更新。以此实现\@Provide对\@Consume状态数据同步。 174 1753. 当\@Consume装饰的数据变化时: 176 177 通过初始渲染的步骤可知,子组件\@Consume持有\@Provide的实例。在\@Consume更新后调用\@Provide的更新方法,将更新的数值同步回\@Provide,以此实现\@Consume向\@Provide的同步更新。 178 179 180 181 182## 限制条件 183 1841. \@Provider/\@Consumer的参数key必须为string类型,否则编译期会报错。 185 186```ts 187// 错误写法,编译报错 188let change: number = 10; 189@Provide(change) message: string = 'Hello'; 190 191// 正确写法 192let change: string = 'change'; 193@Provide(change) message: string = 'Hello'; 194``` 195 1962. \@Consume装饰的变量不能本地初始化,也不能在构造参数中传入初始化,否则编译期会报错。\@Consume仅能通过key来匹配对应的\@Provide变量进行初始化。 197 198【反例】 199 200```ts 201@Component 202struct Child { 203 @Consume msg: string; 204 // 错误写法,不允许本地初始化 205 @Consume msg1: string = 'Hello'; 206 207 build() { 208 Text(this.msg) 209 } 210} 211 212@Entry 213@Component 214struct Parent { 215 @Provide message: string = 'Hello'; 216 217 build() { 218 Column() { 219 // 错误写法,不允许外部传入初始化 220 Child({msg: 'Hello'}) 221 } 222 } 223} 224``` 225 226【正例】 227 228```ts 229@Component 230struct Child { 231 @Consume num: number; 232 233 build() { 234 Column() { 235 Text(`num的值: ${this.num}`) 236 } 237 } 238} 239 240@Entry 241@Component 242struct Parent { 243 @Provide num: number = 10; 244 245 build() { 246 Column() { 247 Text(`num的值: ${this.num}`) 248 Child() 249 } 250 } 251} 252``` 253 2543. \@Provide的key重复定义时,框架会抛出运行时错误,提醒开发者重复定义key,如果开发者需要重复key,可以使用[allowoverride](#provide支持allowoverride参数)。 255 256```ts 257// 错误写法,a重复定义 258@Provide('a') count: number = 10; 259@Provide('a') num: number = 10; 260 261// 正确写法 262@Provide('a') count: number = 10; 263@Provide('b') num: number = 10; 264``` 265 2664. 在初始化\@Consume变量时,如果开发者没有定义对应key的\@Provide变量,框架会抛出运行时错误,提示开发者初始化\@Consume变量失败,原因是无法找到其对应key的\@Provide变量。 267 268【反例】 269 270```ts 271@Component 272struct Child { 273 @Consume num: number; 274 275 build() { 276 Column() { 277 Text(`num的值: ${this.num}`) 278 } 279 } 280} 281 282@Entry 283@Component 284struct Parent { 285 // 错误写法,缺少@Provide 286 num: number = 10; 287 288 build() { 289 Column() { 290 Text(`num的值: ${this.num}`) 291 Child() 292 } 293 } 294} 295``` 296 297【正例】 298 299```ts 300@Component 301struct Child { 302 @Consume num: number; 303 304 build() { 305 Column() { 306 Text(`num的值: ${this.num}`) 307 } 308 } 309} 310 311@Entry 312@Component 313struct Parent { 314 // 正确写法 315 @Provide num: number = 10; 316 317 build() { 318 Column() { 319 Text(`num的值: ${this.num}`) 320 Child() 321 } 322 } 323} 324``` 325 3265. \@Provide与\@Consume不支持装饰Function类型的变量,框架会抛出运行时错误。 327 328 329## 使用场景 330 331在下面的示例是与后代组件双向同步状态\@Provide和\@Consume场景。当分别点击ToDo和ToDoItem组件内Button时,count 的更改会双向同步在ToDo和ToDoItem中。 332 333 334 335```ts 336@Component 337struct ToDoItem { 338 // @Consume装饰的变量通过相同的属性名绑定其祖先组件ToDo内的@Provide装饰的变量 339 @Consume count: number; 340 341 build() { 342 Column() { 343 Text(`count(${this.count})`) 344 Button(`count(${this.count}), count + 1`) 345 .onClick(() => this.count += 1) 346 } 347 .width('50%') 348 } 349} 350 351@Component 352struct ToDoList { 353 build() { 354 Row({ space: 5 }) { 355 ToDoItem() 356 ToDoItem() 357 } 358 } 359} 360 361@Component 362struct ToDoDemo { 363 build() { 364 ToDoList() 365 } 366} 367 368@Entry 369@Component 370struct ToDo { 371 // @Provide装饰的变量index由入口组件ToDo提供其后代组件 372 @Provide count: number = 0; 373 374 build() { 375 Column() { 376 Button(`count(${this.count}), count + 1`) 377 .onClick(() => this.count += 1) 378 ToDoDemo() 379 } 380 } 381} 382``` 383 384### 装饰Map类型变量 385 386> **说明:** 387> 388> 从API version 11开始,\@Provide,\@Consume支持Map类型。 389 390在下面的示例中,message类型为Map<number, string>,点击Button改变message的值,视图会随之刷新。 391 392```ts 393@Component 394struct Child { 395 @Consume message: Map<number, string> 396 397 build() { 398 Column() { 399 ForEach(Array.from(this.message.entries()), (item: [number, string]) => { 400 Text(`${item[0]}`).fontSize(30) 401 Text(`${item[1]}`).fontSize(30) 402 Divider() 403 }) 404 Button('Consume init map').onClick(() => { 405 this.message = new Map([[0, "a"], [1, "b"], [3, "c"]]) 406 }) 407 Button('Consume set new one').onClick(() => { 408 this.message.set(4, "d") 409 }) 410 Button('Consume clear').onClick(() => { 411 this.message.clear() 412 }) 413 Button('Consume replace the first item').onClick(() => { 414 this.message.set(0, "aa") 415 }) 416 Button('Consume delete the first item').onClick(() => { 417 this.message.delete(0) 418 }) 419 } 420 } 421} 422 423 424@Entry 425@Component 426struct MapSample { 427 @Provide message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]) 428 429 build() { 430 Row() { 431 Column() { 432 Button('Provide init map').onClick(() => { 433 this.message = new Map([[0, "a"], [1, "b"], [3, "c"], [4, "d"]]) 434 }) 435 Child() 436 } 437 .width('100%') 438 } 439 .height('100%') 440 } 441} 442``` 443 444### 装饰Set类型变量 445 446> **说明:** 447> 448> 从API version 11开始,\@Provide,\@Consume支持Set类型。 449 450在下面的示例中,message类型为Set\<number\>,点击Button改变message的值,视图会随之刷新。 451 452```ts 453@Component 454struct Child { 455 @Consume message: Set<number> 456 457 build() { 458 Column() { 459 ForEach(Array.from(this.message.entries()), (item: [number, string]) => { 460 Text(`${item[0]}`).fontSize(30) 461 Divider() 462 }) 463 Button('Consume init set').onClick(() => { 464 this.message = new Set([0, 1, 2, 3, 4]) 465 }) 466 Button('Consume set new one').onClick(() => { 467 this.message.add(5) 468 }) 469 Button('Consume clear').onClick(() => { 470 this.message.clear() 471 }) 472 Button('Consume delete the first one').onClick(() => { 473 this.message.delete(0) 474 }) 475 } 476 .width('100%') 477 } 478} 479 480 481@Entry 482@Component 483struct SetSample { 484 @Provide message: Set<number> = new Set([0, 1, 2, 3, 4]) 485 486 build() { 487 Row() { 488 Column() { 489 Button('Provide init set').onClick(() => { 490 this.message = new Set([0, 1, 2, 3, 4, 5]) 491 }) 492 Child() 493 } 494 .width('100%') 495 } 496 .height('100%') 497 } 498} 499``` 500 501### Provide_and_Consume支持联合类型实例 502 503@Provide和@Consume支持联合类型和undefined和null,在下面的示例中,count类型为string | undefined,点击父组件Parent中的Button改变count的属性或者类型,Child中也会对应刷新。 504 505```ts 506@Component 507struct Child { 508 // @Consume装饰的变量通过相同的属性名绑定其祖先组件Ancestors内的@Provide装饰的变量 509 @Consume count: string | undefined; 510 511 build() { 512 Column() { 513 Text(`count(${this.count})`) 514 Button(`count(${this.count}), Child`) 515 .onClick(() => this.count = 'Ancestors') 516 } 517 .width('50%') 518 } 519} 520 521@Component 522struct Parent { 523 build() { 524 Row({ space: 5 }) { 525 Child() 526 } 527 } 528} 529 530@Entry 531@Component 532struct Ancestors { 533 // @Provide装饰的联合类型count由入口组件Ancestors提供其后代组件 534 @Provide count: string | undefined = 'Child'; 535 536 build() { 537 Column() { 538 Button(`count(${this.count}), Child`) 539 .onClick(() => this.count = undefined) 540 Parent() 541 } 542 } 543} 544``` 545 546### \@Provide支持allowOverride参数 547 548allowOverride:\@Provide重写选项。 549 550> **说明:** 551> 552> 从API version 11开始使用。 553 554| 名称 | 类型 | 必填 | 说明 | 555| ------ | ------ | ---- | ------------------------------------------------------------ | 556| allowOverride | string | 否 | 是否允许@Provide重写。允许在同一组件树下通过allowOverride重写同名的@Provide。如果开发者未写allowOverride,定义同名的@Provide,运行时会报错。 | 557 558```ts 559@Component 560struct MyComponent { 561 @Provide({allowOverride : "reviewVotes"}) reviewVotes: number = 10; 562} 563``` 564 565完整示例如下: 566 567```ts 568@Component 569struct GrandSon { 570 // @Consume装饰的变量通过相同的属性名绑定其祖先内的@Provide装饰的变量 571 @Consume("reviewVotes") reviewVotes: number; 572 573 build() { 574 Column() { 575 Text(`reviewVotes(${this.reviewVotes})`) // Text显示10 576 Button(`reviewVotes(${this.reviewVotes}), give +1`) 577 .onClick(() => this.reviewVotes += 1) 578 } 579 .width('50%') 580 } 581} 582 583@Component 584struct Child { 585 @Provide({ allowOverride: "reviewVotes" }) reviewVotes: number = 10; 586 587 build() { 588 Row({ space: 5 }) { 589 GrandSon() 590 } 591 } 592} 593 594@Component 595struct Parent { 596 @Provide({ allowOverride: "reviewVotes" }) reviewVotes: number = 20; 597 598 build() { 599 Child() 600 } 601} 602 603@Entry 604@Component 605struct GrandParent { 606 @Provide("reviewVotes") reviewVotes: number = 40; 607 608 build() { 609 Column() { 610 Button(`reviewVotes(${this.reviewVotes}), give +1`) 611 .onClick(() => this.reviewVotes += 1) 612 Parent() 613 } 614 } 615} 616``` 617 618在上面的示例中: 619- GrandParent声明了@Provide("reviewVotes") reviewVotes: number = 40 620- Parent是GrandParent的子组件,声明@Provide为allowOverride,重写父组件GrandParent的@Provide("reviewVotes") reviewVotes: number = 40。如果不设置allowOverride,则会抛出运行时报错,提示@Provide重复定义。Child同理。 621- GrandSon在初始化@Consume的时候,@Consume装饰的变量通过相同的属性名绑定其最近的祖先的@Provide装饰的变量。 622- GrandSon查找到相同属性名的@Provide在祖先Child中,所以@Consume("reviewVotes") reviewVotes: number初始化数值为10。如果Child中没有定义与@Consume同名的@Provide,则继续向上寻找Parent中的同名@Provide值为20,以此类推。 623- 如果查找到根节点还没有找到key对应的@Provide,则会报初始化@Consume找不到@Provide的报错。 624 625 626## 常见问题 627 628### \@BuilderParam尾随闭包情况下\@Provide未定义错误 629 630在此场景下,CustomWidget执行this.builder()创建子组件CustomWidgetChild时,this指向的是HomePage。因此找不到CustomWidget的\@Provide变量,所以下面示例会报找不到\@Provide错误,和\@BuilderParam连用的时候要谨慎this的指向。 631 632错误示例: 633 634```ts 635class Tmp { 636 a: string = '' 637} 638 639@Entry 640@Component 641struct HomePage { 642 @Builder 643 builder2($$: Tmp) { 644 Text(`${$$.a}测试`) 645 } 646 647 build() { 648 Column() { 649 CustomWidget() { 650 CustomWidgetChild({ builder: this.builder2 }) 651 } 652 } 653 } 654} 655 656@Component 657struct CustomWidget { 658 @Provide('a') a: string = 'abc'; 659 @BuilderParam 660 builder: () => void; 661 662 build() { 663 Column() { 664 Button('你好').onClick(() => { 665 if (this.a == 'ddd') { 666 this.a = 'abc'; 667 } 668 else { 669 this.a = 'ddd'; 670 } 671 672 }) 673 this.builder() 674 } 675 } 676} 677 678@Component 679struct CustomWidgetChild { 680 @Consume('a') a: string; 681 @BuilderParam 682 builder: ($$: Tmp) => void; 683 684 build() { 685 Column() { 686 this.builder({ a: this.a }) 687 } 688 } 689} 690``` 691 692正确示例: 693 694```ts 695class Tmp { 696 name: string = '' 697} 698 699@Entry 700@Component 701struct HomePage { 702 @Provide('name') name: string = 'abc'; 703 704 @Builder 705 builder2($$: Tmp) { 706 Text(`${$$.name}测试`) 707 } 708 709 build() { 710 Column() { 711 Button('你好').onClick(() => { 712 if (this.name == 'ddd') { 713 this.name = 'abc'; 714 } else { 715 this.name = 'ddd'; 716 } 717 }) 718 CustomWidget() { 719 CustomWidgetChild({ builder: this.builder2 }) 720 } 721 } 722 } 723} 724 725@Component 726struct CustomWidget { 727 @BuilderParam 728 builder: () => void; 729 730 build() { 731 this.builder() 732 } 733} 734 735@Component 736struct CustomWidgetChild { 737 @Consume('name') name: string; 738 @BuilderParam 739 builder: ($$: Tmp) => void; 740 741 build() { 742 Column() { 743 this.builder({ name: this.name }) 744 } 745 } 746} 747``` 748 749### 使用a.b(this.object)形式调用,不会触发UI刷新 750 751在build方法内,当@Provide与@Consume装饰的变量是Object类型、且通过a.b(this.object)形式调用时,b方法内传入的是this.object的原生对象,修改其属性,无法触发UI刷新。如下例中,通过静态方法或者使用this调用组件内部方法,修改组件中的this.dog.age与this.dog.name时,UI不会刷新。 752 753【反例】 754 755```ts 756class Animal { 757 name:string; 758 type:string; 759 age: number; 760 761 constructor(name:string, type:string, age:number) { 762 this.name = name; 763 this.type = type; 764 this.age = age; 765 } 766 767 static changeName(animal:Animal) { 768 animal.name = 'Jack'; 769 } 770 static changeAge(animal:Animal) { 771 animal.age += 1; 772 } 773} 774 775@Entry 776@Component 777struct Zoo { 778 @Provide dog:Animal = new Animal('WangCai', 'dog', 2); 779 780 changeZooDogAge(animal:Animal) { 781 animal.age += 2; 782 } 783 784 build() { 785 Column({ space:10 }) { 786 Text(`Zoo: This is a ${this.dog.age}-year-old ${this.dog.type} named ${this.dog.name}.`) 787 .fontColor(Color.Red) 788 .fontSize(30) 789 Button('changeAge') 790 .onClick(()=>{ 791 // 通过静态方法调用,无法触发UI刷新 792 Animal.changeAge(this.dog); 793 }) 794 Button('changeZooDogAge') 795 .onClick(()=>{ 796 // 使用this通过自定义组件内部方法调用,无法触发UI刷新 797 this.changeZooDogAge(this.dog); 798 }) 799 ZooChild() 800 } 801 } 802} 803 804@Component 805struct ZooChild { 806 807 build() { 808 Column({ space:10 }) { 809 Text(`ZooChild`) 810 .fontColor(Color.Blue) 811 .fontSize(30) 812 ZooGrandChild() 813 } 814 } 815} 816 817@Component 818struct ZooGrandChild { 819 @Consume dog:Animal; 820 821 changeZooGrandChildName(animal:Animal) { 822 animal.name = 'Marry'; 823 } 824 825 build() { 826 Column({ space:10 }) { 827 Text(`ZooGrandChild: This is a ${this.dog.age}-year-old ${this.dog.type} named ${this.dog.name}.`) 828 .fontColor(Color.Yellow) 829 .fontSize(30) 830 Button('changeName') 831 .onClick(()=>{ 832 // 通过静态方法调用,无法触发UI刷新 833 Animal.changeName(this.dog); 834 }) 835 Button('changeZooGrandChildName') 836 .onClick(()=>{ 837 // 使用this通过自定义组件内部方法调用,无法触发UI刷新 838 this.changeZooGrandChildName(this.dog); 839 }) 840 } 841 } 842} 843``` 844 845可以通过如下先赋值、再调用新赋值的变量的方式为this.dog加上Proxy代理,实现UI刷新。 846 847【正例】 848 849```ts 850class Animal { 851 name:string; 852 type:string; 853 age: number; 854 855 constructor(name:string, type:string, age:number) { 856 this.name = name; 857 this.type = type; 858 this.age = age; 859 } 860 861 static changeName(animal:Animal) { 862 animal.name = 'Jack'; 863 } 864 static changeAge(animal:Animal) { 865 animal.age += 1; 866 } 867} 868 869@Entry 870@Component 871struct Zoo { 872 @Provide dog:Animal = new Animal('WangCai', 'dog', 2); 873 874 changeZooDogAge(animal:Animal) { 875 animal.age += 2; 876 } 877 878 build() { 879 Column({ space:10 }) { 880 Text(`Zoo: This is a ${this.dog.age}-year-old ${this.dog.type} named ${this.dog.name}.`) 881 .fontColor(Color.Red) 882 .fontSize(30) 883 Button('changeAge') 884 .onClick(()=>{ 885 // 通过赋值添加 Proxy 代理 886 let newDog = this.dog; 887 Animal.changeAge(newDog); 888 }) 889 Button('changeZooDogAge') 890 .onClick(()=>{ 891 // 通过赋值添加 Proxy 代理 892 let newDog = this.dog; 893 this.changeZooDogAge(newDog); 894 }) 895 ZooChild() 896 } 897 } 898} 899 900@Component 901struct ZooChild { 902 903 build() { 904 Column({ space:10 }) { 905 Text(`ZooChild.`) 906 .fontColor(Color.Blue) 907 .fontSize(30) 908 ZooGrandChild() 909 } 910 } 911} 912 913@Component 914struct ZooGrandChild { 915 @Consume dog:Animal; 916 917 changeZooGrandChildName(animal:Animal) { 918 animal.name = 'Marry'; 919 } 920 921 build() { 922 Column({ space:10 }) { 923 Text(`ZooGrandChild: This is a ${this.dog.age}-year-old ${this.dog.type} named ${this.dog.name}.`) 924 .fontColor(Color.Yellow) 925 .fontSize(30) 926 Button('changeName') 927 .onClick(()=>{ 928 // 通过赋值添加 Proxy 代理 929 let newDog = this.dog; 930 Animal.changeName(newDog); 931 }) 932 Button('changeZooGrandChildName') 933 .onClick(()=>{ 934 // 通过赋值添加 Proxy 代理 935 let newDog = this.dog; 936 this.changeZooGrandChildName(newDog); 937 }) 938 } 939 } 940} 941``` 942