1# \@Observed装饰器和\@ObjectLink装饰器:嵌套类对象属性变化 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @liwenzhen3--> 5<!--Designer: @s10021109--> 6<!--Tester: @TerryTsao--> 7<!--Adviser: @zhang_yixin13--> 8 9上文所述的装饰器(包括[\@State](./arkts-state.md)、[\@Prop](./arkts-prop.md)、[\@Link](./arkts-link.md)、[\@Provide和\@Consume](./arkts-provide-and-consume.md)装饰器)仅能观察到第一层的变化,但是在实际应用开发中,应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组、对象数组、嵌套类场景,无法观察到第二层的属性变化。因此,为了实现对嵌套数据结构中深层属性变化的观察,引入了\@Observed和\@ObjectLink装饰器。 10 11\@Observed/\@ObjectLink适用于观察嵌套对象属性的变化,需要开发者对装饰器的基本观察能力有一定的了解,再来对比阅读该文档。建议提前阅读:[\@State](./arkts-state.md)的基本用法。最佳实践请参考[状态管理最佳实践](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-status-management)。 12 13> **说明:** 14> 15> 从API version 9开始,这两个装饰器支持在ArkTS卡片中使用。 16> 17> 从API version 11开始,这两个装饰器支持在原子化服务中使用。 18 19## 概述 20 21\@ObjectLink和\@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步: 22 23- 使用new创建被\@Observed装饰的类,可以观察到类中属性的变化。 24 25- 子组件中\@ObjectLink装饰器装饰的状态变量用于接收\@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被\@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被\@Observed装饰。 26 27- \@Observed用于嵌套类场景中,观察对象类属性变化,要配合自定义组件使用(示例详见[嵌套对象](#嵌套对象)),如果要做数据双/单向同步,需要搭配\@ObjectLink或者\@Prop使用(示例详见[\@Prop与\@ObjectLink的差异](#prop与objectlink的差异))。 28 29 30## 装饰器说明 31 32| \@Observed类装饰器 | 说明 | 33| -------------- | --------------------------------- | 34| 装饰器参数 | 无。 | 35| 类装饰器 | 装饰class。需要放在class的定义前,使用new创建类对象。 | 36 37| \@ObjectLink变量装饰器 | 说明 | 38| ----------------- | ---------------------------------------- | 39| 装饰器参数 | 无。 | 40| 允许装饰的变量类型 | 支持继承Date、[Array](#二维数组)的class实例,API11及以上支持继承[Map](#继承map类)、[Set](#继承set类)的class实例。<br/>API11及以上支持\@Observed装饰类和undefined或null组成的联合类型,比如ClassA \| ClassB, ClassA \| undefined 或者 ClassA \| null, 示例见[@ObjectLink支持联合类型](#objectlink支持联合类型)。<br/>API version 19之前,必须为被\@Observed装饰的class实例。<br/>API version 19及以后,\@ObjectLink也可以被[makeV1Observed](../../reference/apis-arkui/js-apis-StateManagement.md#makev1observed19)的返回值初始化。<br/>支持类型的场景请参考[观察变化](#观察变化)。<br/>**说明:**<br/>\@ObjectLink不支持简单类型,如果开发者需要使用简单类型,可以使用[\@Prop](arkts-prop.md)。 | 41| 被装饰变量的初始值 | 禁止本地初始化。 | 42 43\@ObjectLink的属性可以被改变,但不允许整体赋值,即\@ObjectLink装饰的变量是只读的。 44 45 46```ts 47// 允许@ObjectLink装饰的数据属性赋值 48this.objLink.a= ... 49// 不允许@ObjectLink装饰的数据自身赋值 50this.objLink= ... 51``` 52 53> **说明:** 54> 55> \@ObjectLink装饰的变量不能被赋值,如果要使用赋值操作,请使用[@Prop](arkts-prop.md)。 56> 57> - \@Prop装饰的变量和数据源的关系是单向同步,\@Prop装饰的变量在本地拷贝了数据源,所以它允许本地更改,如果父组件中的数据源有更新,\@Prop装饰的变量在本地的修改将被覆盖。 58> 59> - \@ObjectLink装饰的变量和数据源的关系是双向同步,\@ObjectLink装饰的变量相当于指向数据源的指针。禁止对\@ObjectLink装饰的变量赋值,如果发生\@ObjectLink装饰的变量的赋值,则同步链将被打断。 60 61## 变量的传递/访问规则说明 62 63| \@ObjectLink传递/访问 | 说明 | 64| ----------------- | ---------------------------------------- | 65| 从父组件初始化 | 必须指定。<br/>初始化\@ObjectLink装饰的变量必须同时满足以下场景:<br/>- 类型必须是\@Observed装饰的class。<br/>- 初始化的数值需要是数组项,或者class的属性。<br/>- 同步源的class或者数组必须是[\@State](./arkts-state.md),[\@Link](./arkts-link.md),[\@Provide](./arkts-provide-and-consume.md),[\@Consume](./arkts-provide-and-consume.md)或者\@ObjectLink装饰的数据。<br/>同步源是数组项的示例请参考[对象数组](#对象数组)。初始化的class的示例请参考[嵌套对象](#嵌套对象)。 | 66| 与源对象同步 | 双向。 | 67| 可以初始化子组件 | 允许,可用于初始化常规变量、\@State、\@Link、\@Prop、\@Provide | 68 69 70 **图1** 初始化规则图示 71 72 73 74 75 76## 观察变化和行为表现 77 78 79### 观察变化 80 81\@Observed装饰的类,如果其属性为非简单类型,比如class、Object或者数组,那么这些属性也需要被\@Observed装饰,否则将观察不到这些属性的变化。 82 83 84```ts 85class Child { 86 public num: number; 87 88 constructor(num: number) { 89 this.num = num; 90 } 91} 92 93@Observed 94class Parent { 95 public child: Child; 96 public count: number; 97 98 constructor(child: Child, count: number) { 99 this.child = child; 100 this.count = count; 101 } 102} 103``` 104 105以上示例中,Parent被\@Observed装饰,其成员变量的赋值的变化是可以被观察到的,但对于Child,没有被\@Observed装饰,其属性的修改不能被观察到。若想观察Child的属性修改变化,示例请参考[嵌套对象](#嵌套对象)。 106 107 108```ts 109@ObjectLink parent: Parent; 110 111// 赋值变化可以被观察到 112this.parent.child = new Child(5); 113this.parent.count = 5; 114 115// Child没有被@Observed装饰,其属性的变化观察不到 116this.parent.child.num = 5; 117``` 118 119\@ObjectLink:\@ObjectLink只能接收被\@Observed装饰class的实例,推荐设计单独的自定义组件来渲染每一个数组或对象。此时,对象数组或嵌套对象(属性是对象的对象称为嵌套对象)需要两个自定义组件,一个自定义组件呈现外部数组/对象,另一个自定义组件呈现嵌套在数组/对象内的类对象。可以观察到: 120 121- 其属性的数值的变化,其中属性是指Object.keys(observedObject)返回的所有属性,示例请参考[嵌套对象](#嵌套对象)。 122 123- 如果数据源是数组,则可以观察到数组项的替换,如果数据源是class,可观察到class的属性的变化,示例请参考[对象数组](#对象数组)。 124 125继承Date的class时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性。 126 127```ts 128@Observed 129class DateClass extends Date { 130 constructor(args: number | string) { 131 super(args); 132 } 133} 134 135@Observed 136class NewDate { 137 public data: DateClass; 138 139 constructor(data: DateClass) { 140 this.data = data; 141 } 142} 143 144@Component 145struct Child { 146 label: string = 'date'; 147 @ObjectLink data: DateClass; 148 149 build() { 150 Column() { 151 Button(`child increase the day by 1`) 152 .onClick(() => { 153 this.data.setDate(this.data.getDate() + 1); 154 }) 155 DatePicker({ 156 start: new Date('1970-1-1'), 157 end: new Date('2100-1-1'), 158 selected: this.data 159 }) 160 } 161 } 162} 163 164@Entry 165@Component 166struct Parent { 167 @State newData: NewDate = new NewDate(new DateClass('2023-1-1')); 168 169 build() { 170 Column() { 171 Child({ label: 'date', data: this.newData.data }) 172 173 Button(`parent update the new date`) 174 .onClick(() => { 175 this.newData.data = new DateClass('2023-07-07'); 176 }) 177 Button(`ViewB: this.newData = new NewDate(new DateClass('2023-08-20'))`) 178 .onClick(() => { 179 this.newData = new NewDate(new DateClass('2023-08-20')); 180 }) 181 } 182 } 183} 184``` 185 186继承Map的class时,可以观察到Map整体的赋值,同时可通过调用Map的接口`set`, `clear`, `delete` 更新Map的值。详见[继承Map类](#继承map类)。 187 188继承Set的class时,可以观察到Set整体的赋值,同时可通过调用Set的接口`add`, `clear`, `delete` 更新Set的值。详见[继承Set类](#继承set类)。 189 190 191### 框架行为 192 1931. 初始渲染: 194 195 a. \@Observed装饰的class的实例会被不透明的代理对象包装,代理了class上的属性的setter和getter方法。 196 197 b. 子组件中\@ObjectLink装饰的变量从父组件初始化,接收被\@Observed装饰的class的实例,\@ObjectLink的包装类会将自己注册给\@Observed class。这里的注册行为指的是,\@ObjectLink包装类会向\@Observed实例提供自身的引用,让\@Observed实例将其添加到依赖列表中,以便属性变化时能通知到它。 198 1992. 属性更新:当\@Observed装饰的class属性改变时,会执行代理的setter和getter,然后遍历依赖它的\@ObjectLink包装类,通知数据更新。 200 201 202## 限制条件 203 2041. 使用\@Observed装饰class会改变class原始的原型链,\@Observed和其他类装饰器装饰同一个class可能会带来问题。 205 2062. \@ObjectLink装饰器不能在\@Entry装饰的自定义组件中使用。 207 2083. \@ObjectLink装饰的类型必须是复杂类型,否则会有编译期报错。 209 2104. API version 19前,\@ObjectLink装饰的变量类型必须是显式地由\@Observed装饰的类。如果未指定类型,或不是\@Observed装饰的class,编译期会报错。 211API version 19及以后,\@ObjectLink也可以被[makeV1Observed](../../reference/apis-arkui/js-apis-StateManagement.md#makev1observed19)的返回值初始化,否则会有运行时告警日志。 212 213 ```ts 214 @Observed 215 class Info { 216 count: number; 217 218 constructor(count: number) { 219 this.count = count; 220 } 221 } 222 223 class Test { 224 msg: number; 225 226 constructor(msg: number) { 227 this.msg = msg; 228 } 229 } 230 231 // 错误写法,count未指定类型,编译报错 232 @ObjectLink count; 233 // 错误写法,Test未被@Observed装饰,编译报错 234 @ObjectLink test: Test; 235 236 // 正确写法 237 @ObjectLink count: Info; 238 ``` 239 2405. \@ObjectLink装饰的变量不能本地初始化,仅能通过构造参数从父组件传入初始值,否则编译期会报错。 241 242 ```ts 243 @Observed 244 class Info { 245 count: number; 246 247 constructor(count: number) { 248 this.count = count; 249 } 250 } 251 252 // 错误写法,编译报错 253 @ObjectLink count: Info = new Info(10); 254 255 // 正确写法 256 @ObjectLink count: Info; 257 ``` 258 2596. \@ObjectLink装饰的变量是只读的,不能被赋值,否则会有运行时报错提示Cannot set property when setter is undefined。如果需要对\@ObjectLink装饰的变量进行整体替换,可以在父组件对其进行整体替换。 260 261 【反例】 262 263 ```ts 264 @Observed 265 class Info { 266 count: number; 267 268 constructor(count: number) { 269 this.count = count; 270 } 271 } 272 273 @Component 274 struct Child { 275 @ObjectLink num: Info; 276 277 build() { 278 Column() { 279 Text(`num的值: ${this.num.count}`) 280 .onClick(() => { 281 // 错误写法,@ObjectLink装饰的变量不能被赋值 282 this.num = new Info(10); 283 }) 284 } 285 } 286 } 287 288 @Entry 289 @Component 290 struct Parent { 291 @State num: Info = new Info(10); 292 293 build() { 294 Column() { 295 Text(`count的值: ${this.num.count}`) 296 Child({num: this.num}) 297 } 298 } 299 } 300 ``` 301 302 【正例】 303 304 ```ts 305 @Observed 306 class Info { 307 count: number; 308 309 constructor(count: number) { 310 this.count = count; 311 } 312 } 313 314 @Component 315 struct Child { 316 @ObjectLink num: Info; 317 318 build() { 319 Column() { 320 Text(`num的值: ${this.num.count}`) 321 .onClick(() => { 322 // 正确写法,可以更改@ObjectLink装饰变量的成员属性 323 this.num.count = 20; 324 }) 325 } 326 } 327 } 328 329 @Entry 330 @Component 331 struct Parent { 332 @State num: Info = new Info(10); 333 334 build() { 335 Column() { 336 Text(`count的值: ${this.num.count}`) 337 Button('click') 338 .onClick(() => { 339 // 可以在父组件做整体替换 340 this.num = new Info(30); 341 }) 342 Child({num: this.num}) 343 } 344 } 345 } 346 ``` 347 348 349## 使用场景 350 351### 继承对象 352 353```ts 354@Observed 355class Animal { 356 name: string; 357 age: number; 358 359 constructor(name: string, age: number) { 360 this.name = name; 361 this.age = age; 362 } 363} 364 365@Observed 366class Dog extends Animal { 367 kinds: string; 368 369 constructor(name: string, age: number, kinds: string) { 370 super(name, age); 371 this.kinds = kinds; 372 } 373} 374 375@Entry 376@Component 377struct Index { 378 @State dog: Dog = new Dog('Molly', 2, 'Husky'); 379 380 @Styles 381 pressedStyles() { 382 .backgroundColor('#ffd5d5d5') 383 } 384 385 @Styles 386 normalStyles() { 387 .backgroundColor('#ffffff') 388 } 389 390 build() { 391 Column() { 392 Text(`${this.dog.name}`) 393 .width(320) 394 .margin(10) 395 .fontSize(30) 396 .textAlign(TextAlign.Center) 397 .stateStyles({ 398 pressed: this.pressedStyles, 399 normal: this.normalStyles 400 }) 401 .onClick(() => { 402 this.dog.name = 'DouDou'; 403 }) 404 405 Text(`${this.dog.age}`) 406 .width(320) 407 .margin(10) 408 .fontSize(30) 409 .textAlign(TextAlign.Center) 410 .stateStyles({ 411 pressed: this.pressedStyles, 412 normal: this.normalStyles 413 }) 414 .onClick(() => { 415 this.dog.age = 3; 416 }) 417 418 Text(`${this.dog.kinds}`) 419 .width(320) 420 .margin(10) 421 .fontSize(30) 422 .textAlign(TextAlign.Center) 423 .stateStyles({ 424 pressed: this.pressedStyles, 425 normal: this.normalStyles 426 }) 427 .onClick(() => { 428 this.dog.kinds = 'Samoyed'; 429 }) 430 } 431 } 432} 433``` 434 435 436 437上述示例中,Dog类中的部分属性(name、age)继承自Animal类,直接修改\@State装饰的变量dog中的属性name和age可以正常触发UI刷新。 438 439### 嵌套对象 440 441```ts 442@Observed 443class Book { 444 name: string; 445 446 constructor(name: string) { 447 this.name = name; 448 } 449} 450 451@Observed 452class Bag { 453 book: Book; 454 455 constructor(book: Book) { 456 this.book = book; 457 } 458} 459 460@Component 461struct BookCard { 462 @ObjectLink book: Book; 463 464 build() { 465 Column() { 466 Text(`BookCard: ${this.book.name}`) // 可以观察到name的变化 467 .width(320) 468 .margin(10) 469 .textAlign(TextAlign.Center) 470 471 Button('change book.name') 472 .width(320) 473 .margin(10) 474 .onClick(() => { 475 this.book.name = 'C++'; 476 }) 477 } 478 } 479} 480 481@Entry 482@Component 483struct Index { 484 @State bag: Bag = new Bag(new Book('JS')); 485 486 build() { 487 Column() { 488 Text(`Index: ${this.bag.book.name}`) // 无法观察到name的变化 489 .width(320) 490 .margin(10) 491 .textAlign(TextAlign.Center) 492 493 Button('change bag.book.name') 494 .width(320) 495 .margin(10) 496 .onClick(() => { 497 this.bag.book.name = 'TS'; 498 }) 499 500 BookCard({ book: this.bag.book }) 501 } 502 } 503} 504``` 505 506 507 508上述示例中,点击Index组件中Button,Index组件中的Text组件不刷新,因为该变化属于第二层的变化,\@State无法观察到第二层的变化。然而,Book被\@Observed装饰,Book的属性name可以被\@ObjectLink观察到,所以BookCard组件中Text可以刷新。当然直接点击BookCard组件中Button,Bookcard组件中的Text组件也刷新,因为该变化在BooKCard中属于第一层的变化,亦可被\@ObjectLink观察到。 509 510### 对象数组 511 512对象数组是一种常用的数据结构。以下示例展示了对象数组的用法。 513 514> **说明:** 515> 516> NextID是用来在[ForEach循环渲染](./arkts-rendering-control-foreach.md)过程中,为每个数组元素生成一个唯一且持久的键值,标识对应的组件。 517 518```ts 519let NextID: number = 1; 520 521@Observed 522class Info { 523 public id: number; 524 public info: number; 525 526 constructor(info: number) { 527 this.id = NextID++; 528 this.info = info; 529 } 530} 531 532@Component 533struct Child { 534 // 子组件Child的@ObjectLink的类型是Info 535 @ObjectLink info: Info; 536 label: string = 'ViewChild'; 537 538 build() { 539 Row() { 540 Button(`ViewChild [${this.label}] this.info.info = ${this.info ? this.info.info : 'undefined'}`) 541 .width(320) 542 .margin(10) 543 .onClick(() => { 544 this.info.info += 1; 545 }) 546 } 547 } 548} 549 550@Entry 551@Component 552struct Parent { 553 // Parent中有@State装饰的Info[] 554 @State arrA: Info[] = [new Info(0), new Info(0)]; 555 556 build() { 557 Column() { 558 ForEach(this.arrA, 559 (item: Info) => { 560 Child({ label: `#${item.id}`, info: item }) 561 }, 562 (item: Info): string => item.id.toString() 563 ) 564 // 使用@State装饰的数组的数组项初始化@ObjectLink,其中数组项是被@Observed装饰的Info的实例 565 Child({ label: `ViewChild this.arrA[first]`, info: this.arrA[0] }) 566 Child({ label: `ViewChild this.arrA[last]`, info: this.arrA[this.arrA.length-1] }) 567 568 Button(`ViewParent: reset array`) 569 .width(320) 570 .margin(10) 571 .onClick(() => { 572 this.arrA = [new Info(0), new Info(0)]; 573 }) 574 Button(`ViewParent: push`) 575 .width(320) 576 .margin(10) 577 .onClick(() => { 578 this.arrA.push(new Info(0)); 579 }) 580 Button(`ViewParent: shift`) 581 .width(320) 582 .margin(10) 583 .onClick(() => { 584 if (this.arrA.length > 0) { 585 this.arrA.shift(); 586 } else { 587 console.info('length <= 0'); 588 } 589 }) 590 Button(`ViewParent: item property in middle`) 591 .width(320) 592 .margin(10) 593 .onClick(() => { 594 this.arrA[Math.floor(this.arrA.length / 2)].info = 10; 595 }) 596 Button(`ViewParent: item property in middle`) 597 .width(320) 598 .margin(10) 599 .onClick(() => { 600 this.arrA[Math.floor(this.arrA.length / 2)] = new Info(11); 601 }) 602 } 603 } 604} 605``` 606 607 608 609- this.arrA[Math.floor(this.arrA.length/2)] = new Info(..) :该状态变量的改变触发2次更新: 610 1. ForEach:数组项的赋值导致ForEach的[itemGenerator](../../reference/apis-arkui/arkui-ts/ts-rendering-control-foreach.md)被修改,因此数组项被识别为有更改,ForEach的item builder将执行,创建新的Child组件实例。 611 2. Child({ label: `ViewChild this.arrA[last]`, info: this.arrA[this.arrA.length-1] }):上述更改改变了数组中第二个元素,所以绑定this.arrA[1]的Child将被更新。 612 613- this.arrA.push(new Info(0)) : 将触发2次不同效果的更新: 614 1. ForEach:新添加的Info对象对于ForEach是未知的[itemGenerator](../../reference/apis-arkui/arkui-ts/ts-rendering-control-foreach.md),ForEach的item builder将执行,创建新的Child组件实例。 615 2. Child({ label: `ViewChild this.arrA[last]`, info: this.arrA[this.arrA.length-1] }):数组的最后一项有更改,因此引起第二个Child的实例的更改。对于Child({ label: `ViewChild this.arrA[first]`, info: this.arrA[0] }),数组的更改并没有触发一个数组项更改的改变,所以第一个Child不会刷新。 616 617- this.arrA[Math.floor(this.arrA.length/2)].info:@State无法观察到第二层的变化,但是Info被\@Observed装饰,Info的属性的变化将被\@ObjectLink观察到。 618 619 620### 二维数组 621 622使用\@Observed观察二维数组的变化。可以声明一个被\@Observed装饰的继承Array的子类。 623 624 625```ts 626@Observed 627class ObservedArray<T> extends Array<T> { 628} 629``` 630 631声明一个继承自Array的类ObservedArray\<T\>,并使用new操作符创建ObservedArray\<string\>的实例,该实例可以观察到属性变化。 632 633在下面的示例中,展示了如何利用\@Observed观察二维数组的变化。 634 635```ts 636@Observed 637class ObservedArray<T> extends Array<T> { 638} 639 640@Component 641struct Item { 642 @ObjectLink itemArr: ObservedArray<string>; 643 644 build() { 645 Row() { 646 ForEach(this.itemArr, (item: string, index: number) => { 647 Text(`${index}: ${item}`) 648 .width(100) 649 .height(100) 650 }, (item: string) => item) 651 } 652 } 653} 654 655@Entry 656@Component 657struct IndexPage { 658 @State arr: Array<ObservedArray<string>> = [new ObservedArray<string>('apple'), new ObservedArray<string>('banana'), new ObservedArray<string>('orange')]; 659 660 build() { 661 Column() { 662 ForEach(this.arr, (itemArr: ObservedArray<string>) => { 663 Item({ itemArr: itemArr }) 664 }) 665 666 Divider() 667 668 Button('push two-dimensional array item') 669 .margin(10) 670 .onClick(() => { 671 this.arr[0].push('strawberry'); 672 }) 673 674 Button('push array item') 675 .margin(10) 676 .onClick(() => { 677 this.arr.push(new ObservedArray<string>('pear')); 678 }) 679 680 Button('change two-dimensional array first item') 681 .margin(10) 682 .onClick(() => { 683 this.arr[0][0] = 'APPLE'; 684 }) 685 686 Button('change array first item') 687 .margin(10) 688 .onClick(() => { 689 this.arr[0] = new ObservedArray<string>('watermelon'); 690 }) 691 } 692 } 693} 694``` 695 696API version 19及以后,\@ObjectLink也可以被[makeV1Observed](../../reference/apis-arkui/js-apis-StateManagement.md#makev1observed19)的返回值初始化。所以开发者如果不想额外声明继承Array的类,也可以使用makeV1Observed来达到同样的效果。 697 698完整例子如下。 699 700```ts 701import { UIUtils } from '@kit.ArkUI'; 702 703@Component 704struct Item { 705 @ObjectLink itemArr: Array<string>; 706 707 build() { 708 Row() { 709 ForEach(this.itemArr, (item: string, index: number) => { 710 Text(`${index}: ${item}`) 711 .width(100) 712 .height(100) 713 }, (item: string) => item) 714 } 715 } 716} 717 718@Entry 719@Component 720struct IndexPage { 721 @State arr: Array<Array<string>> = 722 [UIUtils.makeV1Observed(['apple']), UIUtils.makeV1Observed(['banana']), UIUtils.makeV1Observed(['orange'])]; 723 724 build() { 725 Column() { 726 ForEach(this.arr, (itemArr: Array<string>) => { 727 Item({ itemArr: itemArr }) 728 }) 729 730 Divider() 731 732 Button('push two-dimensional array item') 733 .margin(10) 734 .onClick(() => { 735 this.arr[0].push('strawberry'); 736 }) 737 738 Button('push array item') 739 .margin(10) 740 .onClick(() => { 741 this.arr.push(UIUtils.makeV1Observed(['pear'])); 742 }) 743 744 Button('change two-dimensional array first item') 745 .margin(10) 746 .onClick(() => { 747 this.arr[0][0] = 'APPLE'; 748 }) 749 750 Button('change array first item') 751 .margin(10) 752 .onClick(() => { 753 this.arr[0] = UIUtils.makeV1Observed(['watermelon']); 754 }) 755 } 756 } 757} 758``` 759 760 761 762### 继承Map类 763 764> **说明:** 765> 766> 从API version 11开始,\@ObjectLink支持\@Observed装饰Map类型和继承Map类的类型。 767 768在下面的示例中,myMap类型为MyMap\<number, string\>,点击Button改变myMap的属性,视图会随之刷新。 769 770```ts 771@Observed 772class Info { 773 public info: MyMap<number, string>; 774 775 constructor(info: MyMap<number, string>) { 776 this.info = info; 777 } 778} 779 780 781@Observed 782export class MyMap<K, V> extends Map<K, V> { 783 public name: string; 784 785 constructor(name?: string, args?: [K, V][]) { 786 super(args); 787 this.name = name ? name : 'My Map'; 788 } 789 790 getName() { 791 return this.name; 792 } 793} 794 795@Entry 796@Component 797struct MapSampleNested { 798 @State message: Info = new Info(new MyMap('myMap', [[0, 'a'], [1, 'b'], [3, 'c']])); 799 800 build() { 801 Row() { 802 Column() { 803 MapSampleNestedChild({ myMap: this.message.info }) 804 } 805 .width('100%') 806 } 807 .height('100%') 808 } 809} 810 811@Component 812struct MapSampleNestedChild { 813 @ObjectLink myMap: MyMap<number, string>; 814 815 build() { 816 Row() { 817 Column() { 818 ForEach(Array.from(this.myMap.entries()), (item: [number, string]) => { 819 Text(`${item[0]}`).fontSize(30) 820 Text(`${item[1]}`).fontSize(30) 821 Divider().strokeWidth(5) 822 }) 823 824 Button('set new one') 825 .width(200) 826 .margin(10) 827 .onClick(() => { 828 this.myMap.set(4, 'd'); 829 }) 830 Button('clear') 831 .width(200) 832 .margin(10) 833 .onClick(() => { 834 this.myMap.clear(); 835 }) 836 Button('replace the first one') 837 .width(200) 838 .margin(10) 839 .onClick(() => { 840 this.myMap.set(0, 'aa'); 841 }) 842 Button('delete the first one') 843 .width(200) 844 .margin(10) 845 .onClick(() => { 846 this.myMap.delete(0); 847 }) 848 } 849 .width('100%') 850 } 851 .height('100%') 852 } 853} 854``` 855 856 857 858### 继承Set类 859 860> **说明:** 861> 862> 从API version 11开始,\@ObjectLink支持\@Observed装饰Set类型和继承Set类的类型。 863 864在下面的示例中,mySet类型为MySet\<number\>,点击Button改变mySet的属性,视图会随之刷新。 865 866```ts 867@Observed 868class Info { 869 public info: MySet<number>; 870 871 constructor(info: MySet<number>) { 872 this.info = info; 873 } 874} 875 876 877@Observed 878export class MySet<T> extends Set<T> { 879 public name: string; 880 881 constructor(name?: string, args?: T[]) { 882 super(args); 883 this.name = name ? name : 'My Set'; 884 } 885 886 getName() { 887 return this.name; 888 } 889} 890 891@Entry 892@Component 893struct SetSampleNested { 894 @State message: Info = new Info(new MySet('Set', [0, 1, 2, 3, 4])); 895 896 build() { 897 Row() { 898 Column() { 899 SetSampleNestedChild({ mySet: this.message.info }) 900 } 901 .width('100%') 902 } 903 .height('100%') 904 } 905} 906 907@Component 908struct SetSampleNestedChild { 909 @ObjectLink mySet: MySet<number>; 910 911 build() { 912 Row() { 913 Column() { 914 ForEach(Array.from(this.mySet.entries()), (item: [number, number]) => { 915 Text(`${item}`).fontSize(30) 916 Divider() 917 }) 918 Button('set new one') 919 .width(200) 920 .margin(10) 921 .onClick(() => { 922 this.mySet.add(5); 923 }) 924 Button('clear') 925 .width(200) 926 .margin(10) 927 .onClick(() => { 928 this.mySet.clear(); 929 }) 930 Button('delete the first one') 931 .width(200) 932 .margin(10) 933 .onClick(() => { 934 this.mySet.delete(0); 935 }) 936 } 937 .width('100%') 938 } 939 .height('100%') 940 } 941} 942``` 943 944 945 946### ObjectLink支持联合类型 947 948\@ObjectLink支持\@Observed装饰类和undefined或null组成的联合类型,在下面的示例中,count类型为Source | Data | undefined,点击父组件Parent中的Button改变count的属性或者类型,Child中也会对应刷新。 949 950```ts 951@Observed 952class Source { 953 public source: number; 954 955 constructor(source: number) { 956 this.source = source; 957 } 958} 959 960@Observed 961class Data { 962 public data: number; 963 964 constructor(data: number) { 965 this.data = data; 966 } 967} 968 969@Entry 970@Component 971struct Parent { 972 @State count: Source | Data | undefined = new Source(10); 973 974 build() { 975 Column() { 976 Child({ count: this.count }) 977 978 Button('change count property') 979 .margin(10) 980 .onClick(() => { 981 // 判断count的类型,做属性的更新 982 if (this.count instanceof Source) { 983 this.count.source += 1; 984 } else if (this.count instanceof Data) { 985 this.count.data += 1; 986 } else { 987 console.info('count is undefined, cannot change property'); 988 } 989 }) 990 991 Button('change count to Source') 992 .margin(10) 993 .onClick(() => { 994 // 赋值为Source的实例 995 this.count = new Source(100); 996 }) 997 998 Button('change count to Data') 999 .margin(10) 1000 .onClick(() => { 1001 // 赋值为Data的实例 1002 this.count = new Data(100); 1003 }) 1004 1005 Button('change count to undefined') 1006 .margin(10) 1007 .onClick(() => { 1008 // 赋值为undefined 1009 this.count = undefined; 1010 }) 1011 }.width('100%') 1012 } 1013} 1014 1015@Component 1016struct Child { 1017 @ObjectLink count: Source | Data | undefined; 1018 1019 build() { 1020 Column() { 1021 Text(`count is instanceof ${this.count instanceof Source ? 'Source' : 1022 this.count instanceof Data ? 'Data' : 'undefined'}`) 1023 .fontSize(30) 1024 .margin(10) 1025 1026 Text(`count's property is ${this.count instanceof Source ? this.count.source : this.count?.data}`).fontSize(15) 1027 1028 }.width('100%') 1029 } 1030} 1031``` 1032 1033 1034 1035## 常见问题 1036 1037### 在子组件中给\@ObjectLink装饰的变量赋值 1038 1039在子组件中给\@ObjectLink装饰的变量赋值是不允许的。 1040 1041【反例】 1042 1043```ts 1044@Observed 1045class Info { 1046 public info: number = 0; 1047 1048 constructor(info: number) { 1049 this.info = info; 1050 } 1051} 1052 1053@Component 1054struct ObjectLinkChild { 1055 @ObjectLink testNum: Info; 1056 1057 build() { 1058 Text(`ObjectLinkChild testNum ${this.testNum.info}`) 1059 .onClick(() => { 1060 // ObjectLink不能被赋值 1061 this.testNum = new Info(47); 1062 }) 1063 } 1064} 1065 1066@Entry 1067@Component 1068struct Parent { 1069 @State testNum: Info[] = [new Info(1)]; 1070 1071 build() { 1072 Column() { 1073 Text(`Parent testNum ${this.testNum[0].info}`) 1074 .onClick(() => { 1075 this.testNum[0].info += 1; 1076 }) 1077 1078 ObjectLinkChild({ testNum: this.testNum[0] }) 1079 } 1080 } 1081} 1082``` 1083 1084点击ObjectLinkChild给\@ObjectLink装饰的变量赋值: 1085 1086``` 1087this.testNum = new Info(47); 1088``` 1089 1090这是不允许的,对于实现双向数据同步的\@ObjectLink,赋值相当于要更新父组件中的数组项或者class的属性,这个对于 TypeScript/JavaScript是不能实现的。框架对于这种行为会发生运行时报错。 1091 1092【正例】 1093 1094```ts 1095@Observed 1096class Info { 1097 public info: number = 0; 1098 1099 constructor(info: number) { 1100 this.info = info; 1101 } 1102} 1103 1104@Component 1105struct ObjectLinkChild { 1106 @ObjectLink testNum: Info; 1107 1108 build() { 1109 Text(`ObjectLinkChild testNum ${this.testNum.info}`) 1110 .onClick(() => { 1111 // 可以对ObjectLink装饰对象的属性赋值 1112 this.testNum.info = 47; 1113 }) 1114 } 1115} 1116 1117@Entry 1118@Component 1119struct Parent { 1120 @State testNum: Info[] = [new Info(1)]; 1121 1122 build() { 1123 Column() { 1124 Text(`Parent testNum ${this.testNum[0].info}`) 1125 .onClick(() => { 1126 this.testNum[0].info += 1; 1127 }) 1128 1129 ObjectLinkChild({ testNum: this.testNum[0] }) 1130 } 1131 } 1132} 1133``` 1134 1135### 基础嵌套对象属性更改失效 1136 1137在应用开发中,有很多嵌套对象场景,例如,开发者更新了某个属性,但UI没有进行对应的更新。 1138 1139每个装饰器都有观察能力,但并非所有的改变都可以被观察到,只有可以被观察到的变化才会触发UI更新。\@Observed装饰器可以观察到嵌套对象的属性变化,其他装饰器仅能观察到第一层的变化。 1140 1141【反例】 1142 1143下面的例子中,一些UI组件并不会更新。 1144 1145 1146```ts 1147class Parent { 1148 parentId: number; 1149 1150 constructor(parentId: number) { 1151 this.parentId = parentId; 1152 } 1153 1154 getParentId(): number { 1155 return this.parentId; 1156 } 1157 1158 setParentId(parentId: number): void { 1159 this.parentId = parentId; 1160 } 1161} 1162 1163class Child { 1164 childId: number; 1165 1166 constructor(childId: number) { 1167 this.childId = childId; 1168 } 1169 1170 getChildId(): number { 1171 return this.childId; 1172 } 1173 1174 setChildId(childId: number): void { 1175 this.childId = childId; 1176 } 1177} 1178 1179class Cousin extends Parent { 1180 cousinId: number = 47; 1181 child: Child; 1182 1183 constructor(parentId: number, cousinId: number, childId: number) { 1184 super(parentId); 1185 this.cousinId = cousinId; 1186 this.child = new Child(childId); 1187 } 1188 1189 getCousinId(): number { 1190 return this.cousinId; 1191 } 1192 1193 setCousinId(cousinId: number): void { 1194 this.cousinId = cousinId; 1195 } 1196 1197 getChild(): number { 1198 return this.child.getChildId(); 1199 } 1200 1201 setChild(childId: number): void { 1202 return this.child.setChildId(childId); 1203 } 1204} 1205 1206@Entry 1207@Component 1208struct MyView { 1209 @State cousin: Cousin = new Cousin(10, 20, 30); 1210 1211 build() { 1212 Column({ space: 10 }) { 1213 Text(`parentId: ${this.cousin.parentId}`) 1214 Button('Change Parent.parent') 1215 .onClick(() => { 1216 this.cousin.parentId += 1; 1217 }) 1218 1219 Text(`cousinId: ${this.cousin.cousinId}`) 1220 Button('Change Cousin.cousinId') 1221 .onClick(() => { 1222 this.cousin.cousinId += 1; 1223 }) 1224 1225 Text(`childId: ${this.cousin.child.childId}`) 1226 Button('Change Cousin.Child.childId') 1227 .onClick(() => { 1228 // 点击时上面的Text组件不会刷新 1229 this.cousin.child.childId += 1; 1230 }) 1231 } 1232 } 1233} 1234``` 1235 1236- 最后一个Text组件Text('child: ${this.cousin.child.childId}'),当点击该组件时UI不会刷新。 因为,\@State cousin : Cousin 只能观察到this.cousin属性的变化,比如this.cousin.parentId, this.cousin.cousinId 和this.cousin.child的变化,但是无法观察嵌套在属性中的属性,即this.cousin.child.childId(属性childId是内嵌在cousin中的对象Child的属性)。 1237 1238- 为了观察到嵌套于内部的Child的属性,需要做如下改变: 1239 - 构造一个子组件,用于单独渲染Child的实例。 该子组件可以使用\@ObjectLink child : Child或\@Prop child : Child。通常会使用\@ObjectLink,除非子组件需要对其Child对象进行本地修改。 1240 - 嵌套的Child必须用\@Observed装饰。当在Cousin中创建Child对象时(本示例中的Cousin(10, 20, 30)),它将被包装在ES6代理中,当Child属性更改时(this.cousin.child.childId += 1),该代码将修改通知到\@ObjectLink变量。 1241 1242【正例】 1243 1244以下示例使用\@Observed/\@ObjectLink来观察嵌套对象的属性更改。 1245 1246 1247```ts 1248class Parent { 1249 parentId: number; 1250 1251 constructor(parentId: number) { 1252 this.parentId = parentId; 1253 } 1254 1255 getParentId(): number { 1256 return this.parentId; 1257 } 1258 1259 setParentId(parentId: number): void { 1260 this.parentId = parentId; 1261 } 1262} 1263 1264@Observed 1265class Child { 1266 childId: number; 1267 1268 constructor(childId: number) { 1269 this.childId = childId; 1270 } 1271 1272 getChildId(): number { 1273 return this.childId; 1274 } 1275 1276 setChildId(childId: number): void { 1277 this.childId = childId; 1278 } 1279} 1280 1281class Cousin extends Parent { 1282 cousinId: number = 47; 1283 child: Child; 1284 1285 constructor(parentId: number, cousinId: number, childId: number) { 1286 super(parentId); 1287 this.cousinId = cousinId; 1288 this.child = new Child(childId); 1289 } 1290 1291 getCousinId(): number { 1292 return this.cousinId; 1293 } 1294 1295 setCousinId(cousinId: number): void { 1296 this.cousinId = cousinId; 1297 } 1298 1299 getChild(): number { 1300 return this.child.getChildId(); 1301 } 1302 1303 setChild(childId: number): void { 1304 return this.child.setChildId(childId); 1305 } 1306} 1307 1308@Component 1309struct ViewChild { 1310 @ObjectLink child: Child; 1311 1312 build() { 1313 Column({ space: 10 }) { 1314 Text(`childId: ${this.child.getChildId()}`) 1315 Button('Change childId') 1316 .onClick(() => { 1317 this.child.setChildId(this.child.getChildId() + 1); 1318 }) 1319 } 1320 } 1321} 1322 1323@Entry 1324@Component 1325struct MyView { 1326 @State cousin: Cousin = new Cousin(10, 20, 30); 1327 1328 build() { 1329 Column({ space: 10 }) { 1330 Text(`parentId: ${this.cousin.parentId}`) 1331 Button('Change Parent.parentId') 1332 .onClick(() => { 1333 this.cousin.parentId += 1; 1334 }) 1335 1336 Text(`cousinId: ${this.cousin.cousinId}`) 1337 Button('Change Cousin.cousinId') 1338 .onClick(() => { 1339 this.cousin.cousinId += 1; 1340 }) 1341 1342 ViewChild({ child: this.cousin.child }) // Text(`childId: ${this.cousin.child.childId}`)的替代写法 1343 Button('Change Cousin.Child.childId') 1344 .onClick(() => { 1345 this.cousin.child.childId += 1; 1346 }) 1347 } 1348 } 1349} 1350``` 1351 1352### 复杂嵌套对象属性更改失效 1353 1354【反例】 1355 1356以下示例创建了一个带有\@ObjectLink装饰变量的子组件,用于渲染一个含有嵌套属性的ParentCounter,用\@Observed装饰嵌套在ParentCounter中的SubCounter。 1357 1358 1359```ts 1360let nextId = 1; 1361@Observed 1362class SubCounter { 1363 counter: number; 1364 constructor(c: number) { 1365 this.counter = c; 1366 } 1367} 1368@Observed 1369class ParentCounter { 1370 id: number; 1371 counter: number; 1372 subCounter: SubCounter; 1373 incrCounter() { 1374 this.counter++; 1375 } 1376 incrSubCounter(c: number) { 1377 this.subCounter.counter += c; 1378 } 1379 setSubCounter(c: number): void { 1380 this.subCounter.counter = c; 1381 } 1382 constructor(c: number) { 1383 this.id = nextId++; 1384 this.counter = c; 1385 this.subCounter = new SubCounter(c); 1386 } 1387} 1388@Component 1389struct CounterComp { 1390 @ObjectLink value: ParentCounter; 1391 build() { 1392 Column({ space: 10 }) { 1393 Text(`${this.value.counter}`) 1394 .fontSize(25) 1395 .onClick(() => { 1396 this.value.incrCounter(); 1397 }) 1398 Text(`${this.value.subCounter.counter}`) 1399 .onClick(() => { 1400 this.value.incrSubCounter(1); 1401 }) 1402 Divider().height(2) 1403 } 1404 } 1405} 1406@Entry 1407@Component 1408struct ParentComp { 1409 @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1410 build() { 1411 Row() { 1412 Column() { 1413 CounterComp({ value: this.counter[0] }) 1414 CounterComp({ value: this.counter[1] }) 1415 CounterComp({ value: this.counter[2] }) 1416 Divider().height(5) 1417 ForEach(this.counter, 1418 (item: ParentCounter) => { 1419 CounterComp({ value: item }) 1420 }, 1421 (item: ParentCounter) => item.id.toString() 1422 ) 1423 Divider().height(5) 1424 // 第一个点击事件 1425 Text('Parent: incr counter[0].counter') 1426 .fontSize(20).height(50) 1427 .onClick(() => { 1428 this.counter[0].incrCounter(); 1429 // 每次触发时自增10 1430 this.counter[0].incrSubCounter(10); 1431 }) 1432 // 第二个点击事件 1433 Text('Parent: set.counter to 10') 1434 .fontSize(20).height(50) 1435 .onClick(() => { 1436 // 无法将value设置为10,UI不会刷新 1437 this.counter[0].setSubCounter(10); 1438 }) 1439 Text('Parent: reset entire counter') 1440 .fontSize(20).height(50) 1441 .onClick(() => { 1442 this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1443 }) 1444 } 1445 } 1446 } 1447} 1448``` 1449 1450对于Text('Parent: incr counter[0].counter')的onClick事件,this.counter[0].incrSubCounter(10)调用incrSubCounter方法使SubCounter的counter值增加10,UI同步刷新。 1451 1452然而,在Text('Parent: set.counter to 10')的onClick中调用this.counter[0].setSubCounter(10)时,SubCounter的counter值无法重置为10。 1453 1454incrSubCounter和setSubCounter都是同一个SubCounter的函数。在第一个点击处理时调用incrSubCounter可以正确更新UI,而第二个点击处理调用setSubCounter时却没有更新UI。实际上incrSubCounter和setSubCounter两个函数都不能触发Text('${this.value.subCounter.counter}')的更新,因为\@ObjectLink value : ParentCounter仅能观察其代理ParentCounter的属性,对于this.value.subCounter.counter是SubCounter的属性,无法观察到嵌套类的属性。 1455 1456另外,第一个click事件调用this.counter[0].incrCounter()将CounterComp自定义组件中的\@ObjectLink value: ParentCounter标记为已更改,会触发Text('${this.value.subCounter.counter}')的更新。如果在第一个点击事件中删除this.counter[0].incrCounter(),则无法更新UI。 1457 1458【正例】 1459 1460对于上述问题,为了直接观察SubCounter中的属性,以便this.counter[0].setSubCounter(10)操作有效,可以利用下面的方法: 1461 1462 1463```ts 1464CounterComp({ value: this.counter[0] }); // ParentComp组件传递 ParentCounter 给 CounterComp 组件 1465@ObjectLink value: ParentCounter; // @ObjectLink 接收 ParentCounter 1466 1467// CounterChild 是 CounterComp 的子组件,CounterComp 传递 this.value.subCounter 给 CounterChild 组件 1468CounterChild({ subValue: this.value.subCounter }); 1469@ObjectLink subValue: SubCounter; // @ObjectLink 接收 SubCounter 1470``` 1471 1472该方法使得\@ObjectLink分别代理了ParentCounter和SubCounter的属性,这样对于这两个类的属性的变化都可以观察到,即都会对UI视图进行刷新。即使删除了上面所说的this.counter[0].incrCounter(),UI也会进行正确的刷新。 1473 1474该方法可用于实现“两个层级”的观察,即外部对象和内部嵌套对象的观察。但是该方法只能用于\@ObjectLink装饰器,无法作用于\@Prop(\@Prop通过深拷贝传入对象)。详情参考[@Prop与@ObjectLink的差异](#prop与objectlink的差异)。 1475 1476 1477```ts 1478let nextId = 1; 1479 1480@Observed 1481class SubCounter { 1482 counter: number; 1483 1484 constructor(c: number) { 1485 this.counter = c; 1486 } 1487} 1488 1489@Observed 1490class ParentCounter { 1491 id: number; 1492 counter: number; 1493 subCounter: SubCounter; 1494 1495 incrCounter() { 1496 this.counter++; 1497 } 1498 1499 incrSubCounter(c: number) { 1500 this.subCounter.counter += c; 1501 } 1502 1503 setSubCounter(c: number): void { 1504 this.subCounter.counter = c; 1505 } 1506 1507 constructor(c: number) { 1508 this.id = nextId++; 1509 this.counter = c; 1510 this.subCounter = new SubCounter(c); 1511 } 1512} 1513 1514@Component 1515struct CounterComp { 1516 @ObjectLink value: ParentCounter; 1517 1518 build() { 1519 Column({ space: 10 }) { 1520 Text(`${this.value.counter}`) 1521 .fontSize(25) 1522 .onClick(() => { 1523 this.value.incrCounter(); 1524 }) 1525 CounterChild({ subValue: this.value.subCounter }) 1526 Divider().height(2) 1527 } 1528 } 1529} 1530 1531@Component 1532struct CounterChild { 1533 @ObjectLink subValue: SubCounter; 1534 1535 build() { 1536 Text(`${this.subValue.counter}`) 1537 .onClick(() => { 1538 this.subValue.counter += 1; 1539 }) 1540 } 1541} 1542 1543@Entry 1544@Component 1545struct ParentComp { 1546 @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1547 1548 build() { 1549 Row() { 1550 Column() { 1551 CounterComp({ value: this.counter[0] }) 1552 CounterComp({ value: this.counter[1] }) 1553 CounterComp({ value: this.counter[2] }) 1554 Divider().height(5) 1555 ForEach(this.counter, 1556 (item: ParentCounter) => { 1557 CounterComp({ value: item }) 1558 }, 1559 (item: ParentCounter) => item.id.toString() 1560 ) 1561 Divider().height(5) 1562 Text('Parent: reset entire counter') 1563 .fontSize(20).height(50) 1564 .onClick(() => { 1565 this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1566 }) 1567 Text('Parent: incr counter[0].counter') 1568 .fontSize(20).height(50) 1569 .onClick(() => { 1570 this.counter[0].incrCounter(); 1571 this.counter[0].incrSubCounter(10); 1572 }) 1573 Text('Parent: set.counter to 10') 1574 .fontSize(20).height(50) 1575 .onClick(() => { 1576 this.counter[0].setSubCounter(10); 1577 }) 1578 } 1579 } 1580 } 1581} 1582``` 1583 1584### \@Prop与\@ObjectLink的差异 1585 1586\@Prop和\@ObjectLink都可以接收\@Observed装饰的类对象实例。\@Prop对对象进行深拷贝,修改深拷贝后的对象不会影响原对象及其关联的组件。\@ObjectLink获取对象的引用,修改引用对象会影响原对象及其关联的组件。 1587 1588下面的例子中,`UserChild`组件同时使用\@Prop与\@ObjectLink接收了来自父组件的\@Observed装饰的类对象实例作为数据源。对该数据源对象的修改将同时影响\@Prop与\@ObjectLink装饰的变量。依次点击`change @ObjectLink value`按钮和`change @Prop value`按钮可以观察到: 1589 15901. 修改\@ObjectLink装饰的对象内容将影响数据源对象,并重新同步给\@Prop,因此两个Text组件都将刷新。 15912. 修改\@Prop装饰的对象内容仅影响使用该对象的Text2组件,不会影响数据源对象。 1592 1593```ts 1594let nextId = 0; 1595 1596@Observed 1597class User { 1598 id: number; 1599 1600 constructor() { 1601 this.id = nextId++; 1602 } 1603} 1604 1605@Entry 1606@Component 1607struct Index { 1608 @State users: User[] = [new User(), new User(), new User()]; 1609 1610 build() { 1611 Column() { 1612 UserChild({ firstUserByObjectLink: this.users[0], firstUserByProp: this.users[0] }) 1613 } 1614 } 1615} 1616 1617@Component 1618struct UserChild { 1619 @ObjectLink firstUserByObjectLink: User; 1620 @Prop firstUserByProp: User; 1621 1622 build() { 1623 Column() { 1624 // 比较结果为false说明@Prop经过深拷贝后得到的对象与原对象已不是同一个对象 1625 Text(`firstUserByObjectLink equals firstUserByProp? : ${this.firstUserByObjectLink === this.firstUserByProp}`) 1626 Text(`UserChild firstUserByObjectLink.id: ${this.firstUserByObjectLink.id}`) // Text1 1627 Text(`UserChild firstUserByProp.id: ${this.firstUserByProp.id}`) // Text2 1628 Button('change @ObjectLink value') 1629 .onClick(() => { 1630 this.firstUserByObjectLink.id++; 1631 }) 1632 Button('change @Prop value') 1633 .onClick(() => { 1634 this.firstUserByProp.id++; 1635 }) 1636 } 1637 } 1638} 1639``` 1640 1641上面的示例关系如图所示: 1642 1643 1644 1645### 在\@Observed装饰类的构造函数中延时更改成员变量 1646 1647在状态管理中,使用\@Observed装饰类后,会给该类使用一层“代理”进行包装。当在组件中改变该类的成员变量时,会被该代理进行拦截,在更改数据源中值的同时,也会将变化通知给绑定的组件,从而实现观测变化与触发刷新。 1648 1649当开发者在类的构造函数中对成员变量进行赋值或者修改时,此修改不会经过代理(因为是直接对数据源中的值进行修改),也就无法被观测到。所以,如果开发者在类的构造函数中使用定时器修改类中的成员变量,即使该修改成功执行了,也不会触发UI的刷新。 1650 1651【反例】 1652 1653```ts 1654@Observed 1655class RenderClass { 1656 waitToRender: boolean = false; 1657 1658 constructor() { 1659 setTimeout(() => { 1660 this.waitToRender = true; 1661 console.info('更改waitToRender的值为:' + this.waitToRender); 1662 }, 1000) 1663 } 1664} 1665 1666@Entry 1667@Component 1668struct Index { 1669 @State @Watch('renderClassChange') renderClass: RenderClass = new RenderClass(); 1670 @State textColor: Color = Color.Black; 1671 1672 renderClassChange() { 1673 console.info('renderClass的值被更改为:' + this.renderClass.waitToRender); 1674 } 1675 1676 build() { 1677 Row() { 1678 Column() { 1679 Text('renderClass的值为:' + this.renderClass.waitToRender) 1680 .fontSize(20) 1681 .fontColor(this.textColor) 1682 Button('Show') 1683 .onClick(() => { 1684 // 使用其他状态变量强行刷新UI的做法并不推荐,此处仅用来检测waitToRender的值是否更新 1685 this.textColor = Color.Red; 1686 }) 1687 } 1688 .width('100%') 1689 } 1690 .height('100%') 1691 } 1692} 1693``` 1694 1695上文的示例代码中在RenderClass的构造函数中使用定时器在1秒后修改了waitToRender的值,但是不会触发UI的刷新。此时,点击按钮强行刷新Text组件,可以看到waitToRender的值已经被修改成了true。 1696 1697【正例】 1698 1699```ts 1700@Observed 1701class RenderClass { 1702 waitToRender: boolean = false; 1703 1704 constructor() { 1705 } 1706} 1707 1708@Entry 1709@Component 1710struct Index { 1711 @State @Watch('renderClassChange') renderClass: RenderClass = new RenderClass(); 1712 1713 renderClassChange() { 1714 console.info('renderClass的值被更改为:' + this.renderClass.waitToRender); 1715 } 1716 1717 onPageShow() { 1718 setTimeout(() => { 1719 this.renderClass.waitToRender = true; 1720 console.info('更改renderClass的值为:' + this.renderClass.waitToRender); 1721 }, 1000) 1722 } 1723 1724 build() { 1725 Row() { 1726 Column() { 1727 Text('renderClass的值为:' + this.renderClass.waitToRender) 1728 .fontSize(20) 1729 } 1730 .width('100%') 1731 } 1732 .height('100%') 1733 } 1734} 1735``` 1736 1737上文的示例代码将定时器修改移入到组件内,此时界面显示时会先显示“renderClass的值为:false”。待定时器触发时,renderClass的值改变,触发[@Watch](./arkts-watch.md)回调,此时界面刷新显示“renderClass的值为:true”,日志输出“renderClass的值被更改为:true”。 1738 1739因此,更推荐开发者在组件中对\@Observed装饰的类成员变量进行修改,以实现刷新。 1740 1741### \@ObjectLink数据源更新时机 1742 1743```ts 1744@Observed 1745class Person { 1746 name: string = ''; 1747 age: number = 0; 1748 1749 constructor(name: string, age: number) { 1750 this.name = name; 1751 this.age = age; 1752 } 1753} 1754 1755@Observed 1756class Info { 1757 person: Person; 1758 1759 constructor(person: Person) { 1760 this.person = person; 1761 } 1762} 1763 1764@Entry 1765@Component 1766struct Parent { 1767 @State @Watch('onChange01') info: Info = new Info(new Person('Bob', 10)); 1768 1769 onChange01() { 1770 console.info(':::onChange01:' + this.info.person.name); // 2 1771 } 1772 1773 build() { 1774 Column() { 1775 Text(this.info.person.name).height(40) 1776 Child({ 1777 per: this.info.person, clickEvent: () => { 1778 console.info(':::clickEvent before', this.info.person.name); // 1 1779 this.info.person = new Person('Jack', 12); 1780 console.info(':::clickEvent after', this.info.person.name); // 3 1781 } 1782 }) 1783 } 1784 } 1785} 1786 1787@Component 1788struct Child { 1789 @ObjectLink @Watch('onChange02') per: Person; 1790 clickEvent?: () => void; 1791 1792 onChange02() { 1793 console.info(':::onChange02:' + this.per.name); // 5 1794 } 1795 1796 build() { 1797 Column() { 1798 Button(this.per.name) 1799 .height(40) 1800 .onClick(() => { 1801 this.onClickType(); 1802 }) 1803 } 1804 } 1805 1806 private onClickType() { 1807 if (this.clickEvent) { 1808 this.clickEvent(); 1809 } 1810 console.info(':::--------此时Child中的this.per.name值仍然是:' + this.per.name); // 4 1811 } 1812} 1813``` 1814 1815\@ObjectLink的数据源更新依赖其父组件,当父组件中数据源改变引起父组件刷新时,会重新设置子组件\@ObjectLink的数据源。这个过程不是在父组件数据源变化后立刻发生的,而是在父组件实际刷新时才会进行。上述示例中,Parent包含Child,Parent传递箭头函数给Child,在点击时,日志打印顺序是1-2-3-4-5,打印到日志4时,点击事件流程结束,此时仅仅是将子组件Child标记为需要父组件更新的节点,因此日志4打印的this.per.name的值仍为Bob,等到父组件真正更新时,才会更新Child的数据源。 1816 1817当@ObjectLink @Watch('onChange02') per: Person的\@Watch函数执行时,说明\@ObjectLink的数据源已被父组件更新,此时日志5打印的值为更新后的Jack。 1818 1819日志的含义为: 1820- 日志1:对Parent @State @Watch('onChange01') info: Info = new Info(new Person('Bob', 10)) 赋值前。 1821 1822- 日志2:对Parent @State @Watch('onChange01') info: Info = new Info(new Person('Bob', 10)) 赋值,执行其\@Watch函数,同步执行。 1823 1824- 日志3:对Parent @State @Watch('onChange01') info: Info = new Info(new Person('Bob', 10)) 赋值完成。 1825 1826- 日志4:onClickType方法内clickEvent执行完,此时只是将子组件Child标记为需要父组件更新的节点,未将最新的值更新给Child @ObjectLink @Watch('onChange02') per: Person,所以日志4打印的this.per.name的值仍然是Bob。 1827 1828- 日志5:下一次vsync信号触发Child更新,@ObjectLink @Watch('onChange02') per: Person被更新,触发其\@Watch方法,此时@ObjectLink @Watch('onChange02') per: Person为新值Jack。 1829 1830\@Prop父子同步原理与\@ObjectLink一致。 1831 1832当clickEvent中更改this.info.person.name时,修改会立刻生效,此时日志4打印的值是Jack。 1833 1834```ts 1835Child({ 1836 per: this.info.person, clickEvent: () => { 1837 console.info(':::clickEvent before', this.info.person.name); // 1 1838 this.info.person.name = 'Jack'; 1839 console.info(':::clickEvent after', this.info.person.name); // 3 1840 } 1841}) 1842``` 1843 1844此时Parent中Text组件不会刷新,因为this.info.person.name属于两层嵌套。 1845 1846### 使用a.b(this.object)形式调用,不会触发UI刷新 1847 1848在build方法内,当\@Observed与\@ObjectLink联合装饰的变量是Object类型、且通过a.b(this.object)形式调用时,b方法内传入的是this.object的原始对象,修改其属性,无法触发UI刷新。如下例中,通过静态方法或者使用this调用组件内部方法,修改组件中的this.weather.temperature时,UI不会刷新。 1849 1850【反例】 1851 1852```ts 1853@Observed 1854class Weather { 1855 temperature:number; 1856 1857 constructor(temperature:number) { 1858 this.temperature = temperature; 1859 } 1860 1861 static increaseTemperature(weather:Weather) { 1862 weather.temperature++; 1863 } 1864} 1865 1866class Day { 1867 weather:Weather; 1868 week:string; 1869 constructor(weather:Weather, week:string) { 1870 this.weather = weather; 1871 this.week = week; 1872 } 1873} 1874 1875@Entry 1876@Component 1877struct Parent { 1878 @State day1: Day = new Day(new Weather(15), 'Monday'); 1879 1880 build() { 1881 Column({ space:10 }) { 1882 Child({ weather: this.day1.weather}) 1883 } 1884 .height('100%') 1885 .width('100%') 1886 } 1887} 1888 1889@Component 1890struct Child { 1891 @ObjectLink weather: Weather; 1892 1893 reduceTemperature (weather:Weather) { 1894 weather.temperature--; 1895 } 1896 1897 build() { 1898 Column({ space:10 }) { 1899 Text(`The temperature of day1 is ${this.weather.temperature} degrees.`) 1900 .fontSize(20) 1901 Button('increaseTemperature') 1902 .onClick(()=>{ 1903 // 通过静态方法调用,无法触发UI刷新 1904 Weather.increaseTemperature(this.weather); 1905 }) 1906 Button('reduceTemperature') 1907 .onClick(()=>{ 1908 // 使用this通过自定义组件内部方法调用,无法触发UI刷新 1909 this.reduceTemperature(this.weather); 1910 }) 1911 } 1912 .height('100%') 1913 .width('100%') 1914 } 1915} 1916``` 1917 1918可以通过如下先赋值、再调用新赋值的变量的方式为this.weather加上Proxy代理,实现UI刷新。 1919 1920【正例】 1921 1922```ts 1923@Observed 1924class Weather { 1925 temperature:number; 1926 1927 constructor(temperature:number) { 1928 this.temperature = temperature; 1929 } 1930 1931 static increaseTemperature(weather:Weather) { 1932 weather.temperature++; 1933 } 1934} 1935 1936class Day { 1937 weather:Weather; 1938 week:string; 1939 constructor(weather:Weather, week:string) { 1940 this.weather = weather; 1941 this.week = week; 1942 } 1943} 1944 1945@Entry 1946@Component 1947struct Parent { 1948 @State day1: Day = new Day(new Weather(15), 'Monday'); 1949 1950 build() { 1951 Column({ space:10 }) { 1952 Child({ weather: this.day1.weather}) 1953 } 1954 .height('100%') 1955 .width('100%') 1956 } 1957} 1958 1959@Component 1960struct Child { 1961 @ObjectLink weather: Weather; 1962 1963 reduceTemperature (weather:Weather) { 1964 weather.temperature--; 1965 } 1966 1967 build() { 1968 Column({ space:10 }) { 1969 Text(`The temperature of day1 is ${this.weather.temperature} degrees.`) 1970 .fontSize(20) 1971 Button('increaseTemperature') 1972 .onClick(()=>{ 1973 // 通过赋值添加 Proxy 代理 1974 let weather1 = this.weather; 1975 Weather.increaseTemperature(weather1); 1976 }) 1977 Button('reduceTemperature') 1978 .onClick(()=>{ 1979 // 通过赋值添加 Proxy 代理 1980 let weather2 = this.weather; 1981 this.reduceTemperature(weather2); 1982 }) 1983 } 1984 .height('100%') 1985 .width('100%') 1986 } 1987} 1988``` 1989