1# \@Observed装饰器和\@ObjectLink装饰器:嵌套类对象属性变化 2 3 4上文所述的装饰器仅能观察到第一层的变化,但是在实际应用开发中,应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。这就引出了\@Observed/\@ObjectLink装饰器。 5 6 7> **说明:** 8> 9> 从API version 9开始,这两个装饰器支持在ArkTS卡片中使用。 10 11 12## 概述 13 14\@ObjectLink和\@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步: 15 16- 被\@Observed装饰的类,可以被观察到属性的变化; 17 18- 子组件中\@ObjectLink装饰器装饰的状态变量用于接收\@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被\@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被\@Observed装饰。 19 20- 单独使用\@Observed是没有任何作用的,需要搭配\@ObjectLink或者[\@Prop](arkts-prop.md)使用。 21 22 23## 限制条件 24 25- 使用\@Observed装饰class会改变class原始的原型链,\@Observed和其他类装饰器装饰同一个class可能会带来问题。 26 27- \@ObjectLink装饰器不能在\@Entry装饰的自定义组件中使用。 28 29 30## 装饰器说明 31 32| \@Observed类装饰器 | 说明 | 33| -------------- | --------------------------------- | 34| 装饰器参数 | 无 | 35| 类装饰器 | 装饰class。需要放在class的定义前,使用new创建类对象。 | 36 37| \@ObjectLink变量装饰器 | 说明 | 38| ----------------- | ---------------------------------------- | 39| 装饰器参数 | 无 | 40| 允许装饰的变量类型 | 必须为被\@Observed装饰的class实例,必须指定类型。<br/>不支持简单类型,可以使用[\@Prop](arkts-prop.md)。<br/>支持继承Date、Array的class实例,API11及以上支持继承Map、Set的class实例。示例见[观察变化](#观察变化)。<br/>API11及以上支持\@Observed装饰类和undefined或null组成的联合类型,比如ClassA \| ClassB, ClassA \| undefined 或者 ClassA \| null, 示例见[@ObjectLink支持联合类型](#objectlink支持联合类型)。<br/>\@ObjectLink的属性是可以改变的,但是变量的分配是不允许的,也就是说这个装饰器装饰变量是只读的,不能被改变。 | 41| 被装饰变量的初始值 | 不允许。 | 42 43\@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装饰的变量的赋值,则同步链将被打断。因为\@ObjectLink装饰的变量通过数据源(Object)引用来初始化。对于实现双向数据同步的@ObjectLink,赋值相当于更新父组件中的数组项或者class的属性,TypeScript/JavaScript不能实现,会发生运行时报错。 60 61 62## 变量的传递/访问规则说明 63 64| \@ObjectLink传递/访问 | 说明 | 65| ----------------- | ---------------------------------------- | 66| 从父组件初始化 | 必须指定。<br/>初始化\@ObjectLink装饰的变量必须同时满足以下场景:<br/>- 类型必须是\@Observed装饰的class。<br/>- 初始化的数值需要是数组项,或者class的属性。<br/>- 同步源的class或者数组必须是\@State,\@Link,\@Provide,\@Consume或者\@ObjectLink装饰的数据。<br/>同步源是数组项的示例请参考[对象数组](#对象数组)。初始化的class的示例请参考[嵌套对象](#嵌套对象)。 | 67| 与源对象同步 | 双向。 | 68| 可以初始化子组件 | 允许,可用于初始化常规变量、\@State、\@Link、\@Prop、\@Provide | 69 70 71 **图1** 初始化规则图示 72 73 74 75 76 77## 观察变化和行为表现 78 79 80### 观察变化 81 82\@Observed装饰的类,如果其属性为非简单类型,比如class、Object或者数组,也需要被\@Observed装饰,否则将观察不到其属性的变化。 83 84 85```ts 86class ClassA { 87 public c: number; 88 89 constructor(c: number) { 90 this.c = c; 91 } 92} 93 94@Observed 95class ClassB { 96 public a: ClassA; 97 public b: number; 98 99 constructor(a: ClassA, b: number) { 100 this.a = a; 101 this.b = b; 102 } 103} 104``` 105 106以上示例中,ClassB被\@Observed装饰,其成员变量的赋值的变化是可以被观察到的,但对于ClassA,没有被\@Observed装饰,其属性的修改不能被观察到。 107 108 109```ts 110@ObjectLink b: ClassB 111 112// 赋值变化可以被观察到 113this.b.a = new ClassA(5) 114this.b.b = 5 115 116// ClassA没有被@Observed装饰,其属性的变化观察不到 117this.b.a.c = 5 118``` 119 120\@ObjectLink:\@ObjectLink只能接收被\@Observed装饰class的实例,可以观察到: 121 122- 其属性的数值的变化,其中属性是指Object.keys(observedObject)返回的所有属性,示例请参考[嵌套对象](#嵌套对象)。 123 124- 如果数据源是数组,则可以观察到数组item的替换,如果数据源是class,可观察到class的属性的变化,示例请参考[对象数组](#对象数组)。 125 126继承Date的class时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性。 127 128```ts 129@Observed 130class DateClass extends Date { 131 constructor(args: number | string) { 132 super(args) 133 } 134} 135 136@Observed 137class ClassB { 138 public a: DateClass; 139 140 constructor(a: DateClass) { 141 this.a = a; 142 } 143} 144 145@Component 146struct ViewA { 147 label: string = 'date'; 148 @ObjectLink a: DateClass; 149 150 build() { 151 Column() { 152 Button(`child increase the day by 1`) 153 .onClick(() => { 154 this.a.setDate(this.a.getDate() + 1); 155 }) 156 DatePicker({ 157 start: new Date('1970-1-1'), 158 end: new Date('2100-1-1'), 159 selected: this.a 160 }) 161 } 162 } 163} 164 165@Entry 166@Component 167struct ViewB { 168 @State b: ClassB = new ClassB(new DateClass('2023-1-1')); 169 170 build() { 171 Column() { 172 ViewA({ label: 'date', a: this.b.a }) 173 174 Button(`parent update the new date`) 175 .onClick(() => { 176 this.b.a = new DateClass('2023-07-07'); 177 }) 178 Button(`ViewB: this.b = new ClassB(new DateClass('2023-08-20'))`) 179 .onClick(() => { 180 this.b = new ClassB(new DateClass('2023-08-20')); 181 }) 182 } 183 } 184} 185``` 186 187继承Map的class时,可以观察到Map整体的赋值,同时可通过调用Map的接口`set`, `clear`, `delete` 更新Map的值。详见[继承Map类](#继承map类)。 188 189继承Set的class时,可以观察到Set整体的赋值,同时可通过调用Set的接口`add`, `clear`, `delete` 更新Set的值。详见继承Set类。 190 191 192### 框架行为 193 1941. 初始渲染: 195 1. \@Observed装饰的class的实例会被不透明的代理对象包装,代理了class上的属性的setter和getter方法 196 2. 子组件中\@ObjectLink装饰的从父组件初始化,接收被\@Observed装饰的class的实例,\@ObjectLink的包装类会将自己注册给\@Observed class。 197 1982. 属性更新:当\@Observed装饰的class属性改变时,会走到代理的setter和getter,然后遍历依赖它的\@ObjectLink包装类,通知数据更新。 199 200 201## 使用场景 202 203 204### 嵌套对象 205 206以下是嵌套类对象的数据结构。 207 208> **说明:** 209> 210> NextID是用来在[ForEach循环渲染](./arkts-rendering-control-foreach.md)过程中,为每个数组元素生成一个唯一且持久的键值,用于标识对应的组件。 211 212 213```ts 214// objectLinkNestedObjects.ets 215let NextID: number = 1; 216 217@Observed 218class ClassA { 219 public id: number; 220 public c: number; 221 222 constructor(c: number) { 223 this.id = NextID++; 224 this.c = c; 225 } 226} 227 228@Observed 229class ClassB { 230 public a: ClassA; 231 232 constructor(a: ClassA) { 233 this.a = a; 234 } 235} 236 237@Observed 238class ClassD { 239 public c: ClassC; 240 241 constructor(c: ClassC) { 242 this.c = c; 243 } 244} 245 246@Observed 247class ClassC extends ClassA { 248 public k: number; 249 250 constructor(k: number) { 251 // 调用父类方法对k进行处理 252 super(k); 253 this.k = k; 254 } 255} 256``` 257 258 259 以下组件层次结构呈现的是嵌套类对象的数据结构。 260 261```ts 262@Component 263struct ViewC { 264 label: string = 'ViewC1'; 265 @ObjectLink c: ClassC; 266 267 build() { 268 Row() { 269 Column() { 270 Text(`ViewC [${this.label}] this.a.c = ${this.c.c}`) 271 .fontColor('#ffffffff') 272 .backgroundColor('#ff3fc4c4') 273 .height(50) 274 .borderRadius(25) 275 Button(`ViewC: this.c.c add 1`) 276 .backgroundColor('#ff7fcf58') 277 .onClick(() => { 278 this.c.c += 1; 279 console.log('this.c.c:' + this.c.c) 280 }) 281 } 282 .width(300) 283 } 284 } 285} 286 287@Entry 288@Component 289struct ViewB { 290 @State b: ClassB = new ClassB(new ClassA(0)); 291 @State child: ClassD = new ClassD(new ClassC(0)); 292 293 build() { 294 Column() { 295 ViewC({ label: 'ViewC #3', 296 c: this.child.c }) 297 Button(`ViewC: this.child.c.c add 10`) 298 .backgroundColor('#ff7fcf58') 299 .onClick(() => { 300 this.child.c.c += 10 301 console.log('this.child.c.c:' + this.child.c.c) 302 }) 303 } 304 } 305} 306``` 307 308被@Observed装饰的ClassC类,可以观测到继承基类的属性的变化。 309 310 311ViewB中的事件句柄: 312 313 314- this.child.c = new ClassA(0) 和this.b = new ClassB(new ClassA(0)): 对\@State装饰的变量b和其属性的修改。 315 316- this.child.c.c = ... :该变化属于第二层的变化,@State无法观察到第二层的变化,但是ClassA被\@Observed装饰,ClassA的属性c的变化可以被\@ObjectLink观察到。 317 318 319ViewC中的事件句柄: 320 321 322- this.c.c += 1:对\@ObjectLink变量a的修改,将触发Button组件的刷新。\@ObjectLink和\@Prop不同,\@ObjectLink不拷贝来自父组件的数据源,而是在本地构建了指向其数据源的引用。 323 324- \@ObjectLink变量是只读的,this.a = new ClassA(...)是不允许的,因为一旦赋值操作发生,指向数据源的引用将被重置,同步将被打断。 325 326 327### 对象数组 328 329对象数组是一种常用的数据结构。以下示例展示了数组对象的用法。 330 331 332```ts 333let NextID: number = 1; 334 335@Observed 336class ClassA { 337 public id: number; 338 public c: number; 339 340 constructor(c: number) { 341 this.id = NextID++; 342 this.c = c; 343 } 344} 345 346@Component 347struct ViewA { 348 // 子组件ViewA的@ObjectLink的类型是ClassA 349 @ObjectLink a: ClassA; 350 label: string = 'ViewA1'; 351 352 build() { 353 Row() { 354 Button(`ViewA [${this.label}] this.a.c = ${this.a ? this.a.c : "undefined"}`) 355 .onClick(() => { 356 this.a.c += 1; 357 }) 358 } 359 } 360} 361 362@Entry 363@Component 364struct ViewB { 365 // ViewB中有@State装饰的ClassA[] 366 @State arrA: ClassA[] = [new ClassA(0), new ClassA(0)]; 367 368 build() { 369 Column() { 370 ForEach(this.arrA, 371 (item: ClassA) => { 372 ViewA({ label: `#${item.id}`, a: item }) 373 }, 374 (item: ClassA): string => item.id.toString() 375 ) 376 // 使用@State装饰的数组的数组项初始化@ObjectLink,其中数组项是被@Observed装饰的ClassA的实例 377 ViewA({ label: `ViewA this.arrA[first]`, a: this.arrA[0] }) 378 ViewA({ label: `ViewA this.arrA[last]`, a: this.arrA[this.arrA.length-1] }) 379 380 Button(`ViewB: reset array`) 381 .onClick(() => { 382 this.arrA = [new ClassA(0), new ClassA(0)]; 383 }) 384 Button(`ViewB: push`) 385 .onClick(() => { 386 this.arrA.push(new ClassA(0)) 387 }) 388 Button(`ViewB: shift`) 389 .onClick(() => { 390 if (this.arrA.length > 0) { 391 this.arrA.shift() 392 } else { 393 console.log("length <= 0") 394 } 395 }) 396 Button(`ViewB: chg item property in middle`) 397 .onClick(() => { 398 this.arrA[Math.floor(this.arrA.length / 2)].c = 10; 399 }) 400 Button(`ViewB: chg item property in middle`) 401 .onClick(() => { 402 this.arrA[Math.floor(this.arrA.length / 2)] = new ClassA(11); 403 }) 404 } 405 } 406} 407``` 408 409- this.arrA[Math.floor(this.arrA.length/2)] = new ClassA(..) :该状态变量的改变触发2次更新: 410 1. ForEach:数组项的赋值导致ForEach的[itemGenerator](arkts-rendering-control-foreach.md#接口描述)被修改,因此数组项被识别为有更改,ForEach的item builder将执行,创建新的ViewA组件实例。 411 2. ViewA({ label: `ViewA this.arrA[last]`, a: this.arrA[this.arrA.length-1] }):上述更改改变了数组中第二个元素,所以绑定this.arrA[1]的ViewA将被更新。 412 413- this.arrA.push(new ClassA(0)) : 将触发2次不同效果的更新: 414 1. ForEach:新添加的ClassA对象对于ForEach是未知的[itemGenerator](arkts-rendering-control-foreach.md#接口描述),ForEach的item builder将执行,创建新的ViewA组件实例。 415 2. ViewA({ label: `ViewA this.arrA[last]`, a: this.arrA[this.arrA.length-1] }):数组的最后一项有更改,因此引起第二个ViewA的实例的更改。对于ViewA({ label: `ViewA this.arrA[first]`, a: this.arrA[0] }),数组的更改并没有触发一个数组项更改的改变,所以第一个ViewA不会刷新。 416 417- this.arrA[Math.floor(this.arrA.length/2)].c:@State无法观察到第二层的变化,但是ClassA被\@Observed装饰,ClassA的属性的变化将被\@ObjectLink观察到。 418 419 420### 二维数组 421 422使用\@Observed观察二维数组的变化。可以声明一个被\@Observed装饰的继承Array的子类。 423 424 425```ts 426@Observed 427class StringArray extends Array<String> { 428} 429``` 430 431使用new StringArray()来构造StringArray的实例,new运算符使得\@Observed生效,\@Observed观察到StringArray的属性变化。 432 433声明一个从Array扩展的类class StringArray extends Array<String> {},并创建StringArray的实例。\@Observed装饰的类需要使用new运算符来构建class实例。 434 435 436```ts 437@Observed 438class StringArray extends Array<String> { 439} 440 441@Component 442struct ItemPage { 443 @ObjectLink itemArr: StringArray; 444 445 build() { 446 Row() { 447 Text('ItemPage') 448 .width(100).height(100) 449 450 ForEach(this.itemArr, 451 (item: string | Resource) => { 452 Text(item) 453 .width(100).height(100) 454 }, 455 (item: string) => item 456 ) 457 } 458 } 459} 460 461@Entry 462@Component 463struct IndexPage { 464 @State arr: Array<StringArray> = [new StringArray(), new StringArray(), new StringArray()]; 465 466 build() { 467 Column() { 468 ItemPage({ itemArr: this.arr[0] }) 469 ItemPage({ itemArr: this.arr[1] }) 470 ItemPage({ itemArr: this.arr[2] }) 471 Divider() 472 473 474 ForEach(this.arr, 475 (itemArr: StringArray) => { 476 ItemPage({ itemArr: itemArr }) 477 }, 478 (itemArr: string) => itemArr[0] 479 ) 480 481 Divider() 482 483 Button('update') 484 .onClick(() => { 485 console.error('Update all items in arr'); 486 if ((this.arr[0] as Array<String>)[0] !== undefined) { 487 // 正常情况下需要有一个真实的ID来与ForEach一起使用,但此处没有 488 // 因此需要确保推送的字符串是唯一的。 489 this.arr[0].push(`${this.arr[0].slice(-1).pop()}${this.arr[0].slice(-1).pop()}`); 490 this.arr[1].push(`${this.arr[1].slice(-1).pop()}${this.arr[1].slice(-1).pop()}`); 491 this.arr[2].push(`${this.arr[2].slice(-1).pop()}${this.arr[2].slice(-1).pop()}`); 492 } else { 493 this.arr[0].push('Hello'); 494 this.arr[1].push('World'); 495 this.arr[2].push('!'); 496 } 497 }) 498 } 499 } 500} 501``` 502 503### 继承Map类 504 505> **说明:** 506> 507> 从API version 11开始,\@ObjectLink支持\@Observed装饰Map类型和继承Map类的类型。 508 509在下面的示例中,myMap类型为MyMap\<number, string\>,点击Button改变myMap的属性,视图会随之刷新。 510 511```ts 512@Observed 513class ClassA { 514 public a: MyMap<number, string>; 515 516 constructor(a: MyMap<number, string>) { 517 this.a = a; 518 } 519} 520 521 522@Observed 523export class MyMap<K, V> extends Map<K, V> { 524 public name: string; 525 526 constructor(name?: string, args?: [K, V][]) { 527 super(args); 528 this.name = name ? name : "My Map"; 529 } 530 531 getName() { 532 return this.name; 533 } 534} 535 536@Entry 537@Component 538struct MapSampleNested { 539 @State message: ClassA = new ClassA(new MyMap("myMap", [[0, "a"], [1, "b"], [3, "c"]])); 540 541 build() { 542 Row() { 543 Column() { 544 MapSampleNestedChild({ myMap: this.message.a }) 545 } 546 .width('100%') 547 } 548 .height('100%') 549 } 550} 551 552@Component 553struct MapSampleNestedChild { 554 @ObjectLink myMap: MyMap<number, string> 555 556 build() { 557 Row() { 558 Column() { 559 ForEach(Array.from(this.myMap.entries()), (item: [number, string]) => { 560 Text(`${item[0]}`).fontSize(30) 561 Text(`${item[1]}`).fontSize(30) 562 Divider() 563 }) 564 565 Button('set new one').onClick(() => { 566 this.myMap.set(4, "d") 567 }) 568 Button('clear').onClick(() => { 569 this.myMap.clear() 570 }) 571 Button('replace the first one').onClick(() => { 572 this.myMap.set(0, "aa") 573 }) 574 Button('delete the first one').onClick(() => { 575 this.myMap.delete(0) 576 }) 577 } 578 .width('100%') 579 } 580 .height('100%') 581 } 582} 583``` 584 585### 继承Set类 586 587> **说明:** 588> 589> 从API version 11开始,\@ObjectLink支持\@Observed装饰Set类型和继承Set类的类型。 590 591在下面的示例中,mySet类型为MySet\<number\>,点击Button改变mySet的属性,视图会随之刷新。 592 593```ts 594@Observed 595class ClassA { 596 public a: MySet<number>; 597 598 constructor(a: MySet<number>) { 599 this.a = a; 600 } 601} 602 603 604@Observed 605export class MySet<T> extends Set<T> { 606 public name: string; 607 608 constructor(name?: string, args?: T[]) { 609 super(args); 610 this.name = name ? name : "My Set"; 611 } 612 613 getName() { 614 return this.name; 615 } 616} 617 618@Entry 619@Component 620struct SetSampleNested { 621 @State message: ClassA = new ClassA(new MySet("Set", [0, 1, 2, 3, 4])); 622 623 build() { 624 Row() { 625 Column() { 626 SetSampleNestedChild({ mySet: this.message.a }) 627 } 628 .width('100%') 629 } 630 .height('100%') 631 } 632} 633 634@Component 635struct SetSampleNestedChild { 636 @ObjectLink mySet: MySet<number> 637 638 build() { 639 Row() { 640 Column() { 641 ForEach(Array.from(this.mySet.entries()), (item: number) => { 642 Text(`${item}`).fontSize(30) 643 Divider() 644 }) 645 Button('set new one').onClick(() => { 646 this.mySet.add(5) 647 }) 648 Button('clear').onClick(() => { 649 this.mySet.clear() 650 }) 651 Button('delete the first one').onClick(() => { 652 this.mySet.delete(0) 653 }) 654 } 655 .width('100%') 656 } 657 .height('100%') 658 } 659} 660``` 661 662## ObjectLink支持联合类型 663 664@ObjectLink支持@Observed装饰类和undefined或null组成的联合类型,在下面的示例中,count类型为ClassA | ClassB | undefined,点击父组件Page2中的Button改变count的属性或者类型,Child中也会对应刷新。 665 666```ts 667@Observed 668class ClassA { 669 public a: number; 670 671 constructor(a: number) { 672 this.a = a; 673 } 674} 675 676@Observed 677class ClassB { 678 public b: number; 679 680 constructor(b: number) { 681 this.b = b; 682 } 683} 684 685@Entry 686@Component 687struct Page2 { 688 @State count: ClassA | ClassB | undefined = new ClassA(10) 689 690 build() { 691 Column() { 692 Child({ count: this.count }) 693 694 Button('change count property') 695 .onClick(() => { 696 // 判断count的类型,做属性的更新 697 if (this.count instanceof ClassA) { 698 this.count.a += 1 699 } else if (this.count instanceof ClassB) { 700 this.count.b += 1 701 } else { 702 console.info('count is undefined, cannot change property') 703 } 704 }) 705 706 Button('change count to ClassA') 707 .onClick(() => { 708 // 赋值为ClassA的实例 709 this.count = new ClassA(100) 710 }) 711 712 Button('change count to ClassB') 713 .onClick(() => { 714 // 赋值为ClassA的实例 715 this.count = new ClassB(100) 716 }) 717 718 Button('change count to undefined') 719 .onClick(() => { 720 // 赋值为undefined 721 this.count = undefined 722 }) 723 }.width('100%') 724 } 725} 726 727@Component 728struct Child { 729 @ObjectLink count: ClassA | ClassB | undefined 730 731 build() { 732 Column() { 733 Text(`count is instanceof ${this.count instanceof ClassA ? 'ClassA' : this.count instanceof ClassB ? 'ClassB' : 'undefined'}`) 734 .fontSize(30) 735 736 Text(`count's property is ${this.count instanceof ClassA ? this.count.a : this.count?.b}`).fontSize(15) 737 738 }.width('100%') 739 } 740} 741``` 742 743## 常见问题 744 745### 在子组件中给@ObjectLink装饰的变量赋值 746 747在子组件中给@ObjectLink装饰的变量赋值是不允许的。 748 749【反例】 750 751```ts 752@Observed 753class ClassA { 754 public c: number = 0; 755 756 constructor(c: number) { 757 this.c = c; 758 } 759} 760 761@Component 762struct ObjectLinkChild { 763 @ObjectLink testNum: ClassA; 764 765 build() { 766 Text(`ObjectLinkChild testNum ${this.testNum.c}`) 767 .onClick(() => { 768 // ObjectLink不能被赋值 769 this.testNum = new ClassA(47); 770 }) 771 } 772} 773 774@Entry 775@Component 776struct Parent { 777 @State testNum: ClassA[] = [new ClassA(1)]; 778 779 build() { 780 Column() { 781 Text(`Parent testNum ${this.testNum[0].c}`) 782 .onClick(() => { 783 this.testNum[0].c += 1; 784 }) 785 786 ObjectLinkChild({ testNum: this.testNum[0] }) 787 } 788 } 789} 790``` 791 792点击ObjectLinkChild给\@ObjectLink装饰的变量赋值: 793 794``` 795this.testNum = new ClassA(47); 796``` 797 798这是不允许的,对于实现双向数据同步的\@ObjectLink,赋值相当于要更新父组件中的数组项或者class的属性,这个对于 TypeScript/JavaScript是不能实现的。框架对于这种行为会发生运行时报错。 799 800【正例】 801 802```ts 803@Observed 804class ClassA { 805 public c: number = 0; 806 807 constructor(c: number) { 808 this.c = c; 809 } 810} 811 812@Component 813struct ObjectLinkChild { 814 @ObjectLink testNum: ClassA; 815 816 build() { 817 Text(`ObjectLinkChild testNum ${this.testNum.c}`) 818 .onClick(() => { 819 // 可以对ObjectLink装饰对象的属性赋值 820 this.testNum.c = 47; 821 }) 822 } 823} 824 825@Entry 826@Component 827struct Parent { 828 @State testNum: ClassA[] = [new ClassA(1)]; 829 830 build() { 831 Column() { 832 Text(`Parent testNum ${this.testNum[0].c}`) 833 .onClick(() => { 834 this.testNum[0].c += 1; 835 }) 836 837 ObjectLinkChild({ testNum: this.testNum[0] }) 838 } 839 } 840} 841``` 842 843### 基础嵌套对象属性更改失效 844 845在应用开发中,有很多嵌套对象场景,例如,开发者更新了某个属性,但UI没有进行对应的更新。 846 847每个装饰器都有自己可以观察的能力,并不是所有的改变都可以被观察到,只有可以被观察到的变化才会进行UI更新。\@Observed装饰器可以观察到嵌套对象的属性变化,其他装饰器仅能观察到第二层的变化。 848 849【反例】 850 851下面的例子中,一些UI组件并不会更新。 852 853 854```ts 855class ClassA { 856 a: number; 857 858 constructor(a: number) { 859 this.a = a; 860 } 861 862 getA(): number { 863 return this.a; 864 } 865 866 setA(a: number): void { 867 this.a = a; 868 } 869} 870 871class ClassC { 872 c: number; 873 874 constructor(c: number) { 875 this.c = c; 876 } 877 878 getC(): number { 879 return this.c; 880 } 881 882 setC(c: number): void { 883 this.c = c; 884 } 885} 886 887class ClassB extends ClassA { 888 b: number = 47; 889 c: ClassC; 890 891 constructor(a: number, b: number, c: number) { 892 super(a); 893 this.b = b; 894 this.c = new ClassC(c); 895 } 896 897 getB(): number { 898 return this.b; 899 } 900 901 setB(b: number): void { 902 this.b = b; 903 } 904 905 getC(): number { 906 return this.c.getC(); 907 } 908 909 setC(c: number): void { 910 return this.c.setC(c); 911 } 912} 913 914 915@Entry 916@Component 917struct MyView { 918 @State b: ClassB = new ClassB(10, 20, 30); 919 920 build() { 921 Column({ space: 10 }) { 922 Text(`a: ${this.b.a}`) 923 Button("Change ClassA.a") 924 .onClick(() => { 925 this.b.a += 1; 926 }) 927 928 Text(`b: ${this.b.b}`) 929 Button("Change ClassB.b") 930 .onClick(() => { 931 this.b.b += 1; 932 }) 933 934 Text(`c: ${this.b.c.c}`) 935 Button("Change ClassB.ClassC.c") 936 .onClick(() => { 937 // 点击时上面的Text组件不会刷新 938 this.b.c.c += 1; 939 }) 940 } 941 } 942} 943``` 944 945- 最后一个Text组件Text('c: ${this.b.c.c}'),当点击该组件时UI不会刷新。 因为,\@State b : ClassB 只能观察到this.b属性的变化,比如this.b.a, this.b.b 和this.b.c的变化,但是无法观察嵌套在属性中的属性,即this.b.c.c(属性c是内嵌在b中的对象classC的属性)。 946 947- 为了观察到嵌套于内部的ClassC的属性,需要做如下改变: 948 - 构造一个子组件,用于单独渲染ClassC的实例。 该子组件可以使用\@ObjectLink c : ClassC或\@Prop c : ClassC。通常会使用\@ObjectLink,除非子组件需要对其ClassC对象进行本地修改。 949 - 嵌套的ClassC必须用\@Observed装饰。当在ClassB中创建ClassC对象时(本示例中的ClassB(10, 20, 30)),它将被包装在ES6代理中,当ClassC属性更改时(this.b.c.c += 1),该代码将修改通知到\@ObjectLink变量。 950 951【正例】 952 953以下示例使用\@Observed/\@ObjectLink来观察嵌套对象的属性更改。 954 955 956```ts 957class ClassA { 958 a: number; 959 960 constructor(a: number) { 961 this.a = a; 962 } 963 964 getA(): number { 965 return this.a; 966 } 967 968 setA(a: number): void { 969 this.a = a; 970 } 971} 972 973@Observed 974class ClassC { 975 c: number; 976 977 constructor(c: number) { 978 this.c = c; 979 } 980 981 getC(): number { 982 return this.c; 983 } 984 985 setC(c: number): void { 986 this.c = c; 987 } 988} 989 990class ClassB extends ClassA { 991 b: number = 47; 992 c: ClassC; 993 994 constructor(a: number, b: number, c: number) { 995 super(a); 996 this.b = b; 997 this.c = new ClassC(c); 998 } 999 1000 getB(): number { 1001 return this.b; 1002 } 1003 1004 setB(b: number): void { 1005 this.b = b; 1006 } 1007 1008 getC(): number { 1009 return this.c.getC(); 1010 } 1011 1012 setC(c: number): void { 1013 return this.c.setC(c); 1014 } 1015} 1016 1017@Component 1018struct ViewClassC { 1019 @ObjectLink c: ClassC; 1020 1021 build() { 1022 Column({ space: 10 }) { 1023 Text(`c: ${this.c.getC()}`) 1024 Button("Change C") 1025 .onClick(() => { 1026 this.c.setC(this.c.getC() + 1); 1027 }) 1028 } 1029 } 1030} 1031 1032@Entry 1033@Component 1034struct MyView { 1035 @State b: ClassB = new ClassB(10, 20, 30); 1036 1037 build() { 1038 Column({ space: 10 }) { 1039 Text(`a: ${this.b.a}`) 1040 Button("Change ClassA.a") 1041 .onClick(() => { 1042 this.b.a += 1; 1043 }) 1044 1045 Text(`b: ${this.b.b}`) 1046 Button("Change ClassB.b") 1047 .onClick(() => { 1048 this.b.b += 1; 1049 }) 1050 1051 ViewClassC({ c: this.b.c }) // Text(`c: ${this.b.c.c}`)的替代写法 1052 Button("Change ClassB.ClassC.c") 1053 .onClick(() => { 1054 this.b.c.c += 1; 1055 }) 1056 } 1057 } 1058} 1059``` 1060 1061### 复杂嵌套对象属性更改失效 1062 1063【反例】 1064 1065以下示例创建了一个带有\@ObjectLink装饰变量的子组件,用于渲染一个含有嵌套属性的ParentCounter,用\@Observed装饰嵌套在ParentCounter中的SubCounter。 1066 1067 1068```ts 1069let nextId = 1; 1070@Observed 1071class SubCounter { 1072 counter: number; 1073 constructor(c: number) { 1074 this.counter = c; 1075 } 1076} 1077@Observed 1078class ParentCounter { 1079 id: number; 1080 counter: number; 1081 subCounter: SubCounter; 1082 incrCounter() { 1083 this.counter++; 1084 } 1085 incrSubCounter(c: number) { 1086 this.subCounter.counter += c; 1087 } 1088 setSubCounter(c: number): void { 1089 this.subCounter.counter = c; 1090 } 1091 constructor(c: number) { 1092 this.id = nextId++; 1093 this.counter = c; 1094 this.subCounter = new SubCounter(c); 1095 } 1096} 1097@Component 1098struct CounterComp { 1099 @ObjectLink value: ParentCounter; 1100 build() { 1101 Column({ space: 10 }) { 1102 Text(`${this.value.counter}`) 1103 .fontSize(25) 1104 .onClick(() => { 1105 this.value.incrCounter(); 1106 }) 1107 Text(`${this.value.subCounter.counter}`) 1108 .onClick(() => { 1109 this.value.incrSubCounter(1); 1110 }) 1111 Divider().height(2) 1112 } 1113 } 1114} 1115@Entry 1116@Component 1117struct ParentComp { 1118 @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1119 build() { 1120 Row() { 1121 Column() { 1122 CounterComp({ value: this.counter[0] }) 1123 CounterComp({ value: this.counter[1] }) 1124 CounterComp({ value: this.counter[2] }) 1125 Divider().height(5) 1126 ForEach(this.counter, 1127 (item: ParentCounter) => { 1128 CounterComp({ value: item }) 1129 }, 1130 (item: ParentCounter) => item.id.toString() 1131 ) 1132 Divider().height(5) 1133 // 第一个点击事件 1134 Text('Parent: incr counter[0].counter') 1135 .fontSize(20).height(50) 1136 .onClick(() => { 1137 this.counter[0].incrCounter(); 1138 // 每次触发时自增10 1139 this.counter[0].incrSubCounter(10); 1140 }) 1141 // 第二个点击事件 1142 Text('Parent: set.counter to 10') 1143 .fontSize(20).height(50) 1144 .onClick(() => { 1145 // 无法将value设置为10,UI不会刷新 1146 this.counter[0].setSubCounter(10); 1147 }) 1148 Text('Parent: reset entire counter') 1149 .fontSize(20).height(50) 1150 .onClick(() => { 1151 this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1152 }) 1153 } 1154 } 1155 } 1156} 1157``` 1158 1159对于Text('Parent: incr counter[0].counter')的onClick事件,this.counter[0].incrSubCounter(10)调用incrSubCounter方法使SubCounter的counter值增加10,UI同步刷新。 1160 1161但是,在Text('Parent: set.counter to 10')的onClick中调用this.counter[0].setSubCounter(10),SubCounter的counter值却无法重置为10。 1162 1163incrSubCounter和setSubCounter都是同一个SubCounter的函数。在第一个点击处理时调用incrSubCounter可以正确更新UI,而第二个点击处理调用setSubCounter时却没有更新UI。实际上incrSubCounter和setSubCounter两个函数都不能触发Text('${this.value.subCounter.counter}')的更新,因为\@ObjectLink value : ParentCounter仅能观察其代理ParentCounter的属性,对于this.value.subCounter.counter是SubCounter的属性,无法观察到嵌套类的属性。 1164 1165但是,第一个click事件调用this.counter[0].incrCounter()将CounterComp自定义组件中\@ObjectLink value: ParentCounter标记为已更改。此时触发Text('${this.value.subCounter.counter}')的更新。 如果在第一个点击事件中删除this.counter[0].incrCounter(),也无法更新UI。 1166 1167【正例】 1168 1169对于上述问题,为了直接观察SubCounter中的属性,以便this.counter[0].setSubCounter(10)操作有效,可以利用下面的方法: 1170 1171 1172```ts 1173@ObjectLink value:ParentCounter = new ParentCounter(0); 1174@ObjectLink subValue:SubCounter = new SubCounter(0); 1175``` 1176 1177该方法使得\@ObjectLink分别代理了ParentCounter和SubCounter的属性,这样对于这两个类的属性的变化都可以观察到,即都会对UI视图进行刷新。即使删除了上面所说的this.counter[0].incrCounter(),UI也会进行正确的刷新。 1178 1179该方法可用于实现“两个层级”的观察,即外部对象和内部嵌套对象的观察。但是该方法只能用于\@ObjectLink装饰器,无法作用于\@Prop(\@Prop通过深拷贝传入对象)。详情参考@Prop与@ObjectLink的差异。 1180 1181 1182```ts 1183let nextId = 1; 1184 1185@Observed 1186class SubCounter { 1187 counter: number; 1188 1189 constructor(c: number) { 1190 this.counter = c; 1191 } 1192} 1193 1194@Observed 1195class ParentCounter { 1196 id: number; 1197 counter: number; 1198 subCounter: SubCounter; 1199 1200 incrCounter() { 1201 this.counter++; 1202 } 1203 1204 incrSubCounter(c: number) { 1205 this.subCounter.counter += c; 1206 } 1207 1208 setSubCounter(c: number): void { 1209 this.subCounter.counter = c; 1210 } 1211 1212 constructor(c: number) { 1213 this.id = nextId++; 1214 this.counter = c; 1215 this.subCounter = new SubCounter(c); 1216 } 1217} 1218 1219@Component 1220struct CounterComp { 1221 @ObjectLink value: ParentCounter; 1222 1223 build() { 1224 Column({ space: 10 }) { 1225 Text(`${this.value.counter}`) 1226 .fontSize(25) 1227 .onClick(() => { 1228 this.value.incrCounter(); 1229 }) 1230 CounterChild({ subValue: this.value.subCounter }) 1231 Divider().height(2) 1232 } 1233 } 1234} 1235 1236@Component 1237struct CounterChild { 1238 @ObjectLink subValue: SubCounter; 1239 1240 build() { 1241 Text(`${this.subValue.counter}`) 1242 .onClick(() => { 1243 this.subValue.counter += 1; 1244 }) 1245 } 1246} 1247 1248@Entry 1249@Component 1250struct ParentComp { 1251 @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1252 1253 build() { 1254 Row() { 1255 Column() { 1256 CounterComp({ value: this.counter[0] }) 1257 CounterComp({ value: this.counter[1] }) 1258 CounterComp({ value: this.counter[2] }) 1259 Divider().height(5) 1260 ForEach(this.counter, 1261 (item: ParentCounter) => { 1262 CounterComp({ value: item }) 1263 }, 1264 (item: ParentCounter) => item.id.toString() 1265 ) 1266 Divider().height(5) 1267 Text('Parent: reset entire counter') 1268 .fontSize(20).height(50) 1269 .onClick(() => { 1270 this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1271 }) 1272 Text('Parent: incr counter[0].counter') 1273 .fontSize(20).height(50) 1274 .onClick(() => { 1275 this.counter[0].incrCounter(); 1276 this.counter[0].incrSubCounter(10); 1277 }) 1278 Text('Parent: set.counter to 10') 1279 .fontSize(20).height(50) 1280 .onClick(() => { 1281 this.counter[0].setSubCounter(10); 1282 }) 1283 } 1284 } 1285 } 1286} 1287``` 1288 1289### \@Prop与\@ObjectLink的差异 1290 1291在下面的示例代码中,\@ObjectLink装饰的变量是对数据源的引用,即在this.value.subValue和this.subValue都是同一个对象的不同引用,所以在点击CounterComp的click handler,改变this.value.subCounter.counter,this.subValue.counter也会改变,对应的组件Text(`this.subValue.counter: ${this.subValue.counter}`)会刷新。 1292 1293 1294```ts 1295let nextId = 1; 1296 1297@Observed 1298class SubCounter { 1299 counter: number; 1300 1301 constructor(c: number) { 1302 this.counter = c; 1303 } 1304} 1305 1306@Observed 1307class ParentCounter { 1308 id: number; 1309 counter: number; 1310 subCounter: SubCounter; 1311 1312 incrCounter() { 1313 this.counter++; 1314 } 1315 1316 incrSubCounter(c: number) { 1317 this.subCounter.counter += c; 1318 } 1319 1320 setSubCounter(c: number): void { 1321 this.subCounter.counter = c; 1322 } 1323 1324 constructor(c: number) { 1325 this.id = nextId++; 1326 this.counter = c; 1327 this.subCounter = new SubCounter(c); 1328 } 1329} 1330 1331@Component 1332struct CounterComp { 1333 @ObjectLink value: ParentCounter; 1334 1335 build() { 1336 Column({ space: 10 }) { 1337 CountChild({ subValue: this.value.subCounter }) 1338 Text(`this.value.counter:increase 7 `) 1339 .fontSize(30) 1340 .onClick(() => { 1341 // click handler, Text(`this.subValue.counter: ${this.subValue.counter}`) will update 1342 this.value.incrSubCounter(7); 1343 }) 1344 Divider().height(2) 1345 } 1346 } 1347} 1348 1349@Component 1350struct CountChild { 1351 @ObjectLink subValue: SubCounter; 1352 1353 build() { 1354 Text(`this.subValue.counter: ${this.subValue.counter}`) 1355 .fontSize(30) 1356 } 1357} 1358 1359@Entry 1360@Component 1361struct ParentComp { 1362 @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1363 1364 build() { 1365 Row() { 1366 Column() { 1367 CounterComp({ value: this.counter[0] }) 1368 CounterComp({ value: this.counter[1] }) 1369 CounterComp({ value: this.counter[2] }) 1370 Divider().height(5) 1371 ForEach(this.counter, 1372 (item: ParentCounter) => { 1373 CounterComp({ value: item }) 1374 }, 1375 (item: ParentCounter) => item.id.toString() 1376 ) 1377 Divider().height(5) 1378 Text('Parent: reset entire counter') 1379 .fontSize(20).height(50) 1380 .onClick(() => { 1381 this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1382 }) 1383 Text('Parent: incr counter[0].counter') 1384 .fontSize(20).height(50) 1385 .onClick(() => { 1386 this.counter[0].incrCounter(); 1387 this.counter[0].incrSubCounter(10); 1388 }) 1389 Text('Parent: set.counter to 10') 1390 .fontSize(20).height(50) 1391 .onClick(() => { 1392 this.counter[0].setSubCounter(10); 1393 }) 1394 } 1395 } 1396 } 1397} 1398``` 1399 1400\@ObjectLink图示如下: 1401 1402 1403 1404【反例】 1405 1406如果用\@Prop替代\@ObjectLink。点击第一个click handler,UI刷新正常。但是点击第二个onClick事件,\@Prop 对变量做了一个本地拷贝,CounterComp的第一个Text并不会刷新。 1407 1408 this.value.subCounter和this.subValue并不是同一个对象。所以this.value.subCounter的改变,并没有改变this.subValue的拷贝对象,Text(`this.subValue.counter: ${this.subValue.counter}`)不会刷新。 1409 1410```ts 1411@Component 1412struct CounterComp { 1413 @Prop value: ParentCounter = new ParentCounter(0); 1414 @Prop subValue: SubCounter = new SubCounter(0); 1415 build() { 1416 Column({ space: 10 }) { 1417 Text(`this.subValue.counter: ${this.subValue.counter}`) 1418 .fontSize(20) 1419 .onClick(() => { 1420 // 1st click handler 1421 this.subValue.counter += 7; 1422 }) 1423 Text(`this.value.counter:increase 7 `) 1424 .fontSize(20) 1425 .onClick(() => { 1426 // 2nd click handler 1427 this.value.incrSubCounter(7); 1428 }) 1429 Divider().height(2) 1430 } 1431 } 1432} 1433``` 1434 1435\@Prop拷贝的关系图示如下: 1436 1437 1438 1439【正例】 1440 1441可以通过从ParentComp到CounterComp仅拷贝一份\@Prop value: ParentCounter,同时必须避免再多拷贝一份SubCounter。 1442 1443- 在CounterComp组件中只使用一个\@Prop counter:Counter。 1444 1445- 添加另一个子组件SubCounterComp,其中包含\@ObjectLink subCounter: SubCounter。此\@ObjectLink可确保观察到SubCounter对象属性更改,并且UI更新正常。 1446 1447- \@ObjectLink subCounter: SubCounter与CounterComp中的\@Prop counter:Counter的this.counter.subCounter共享相同的SubCounter对象。 1448 1449 1450 1451```ts 1452let nextId = 1; 1453 1454@Observed 1455class SubCounter { 1456 counter: number; 1457 constructor(c: number) { 1458 this.counter = c; 1459 } 1460} 1461 1462@Observed 1463class ParentCounter { 1464 id: number; 1465 counter: number; 1466 subCounter: SubCounter; 1467 incrCounter() { 1468 this.counter++; 1469 } 1470 incrSubCounter(c: number) { 1471 this.subCounter.counter += c; 1472 } 1473 setSubCounter(c: number): void { 1474 this.subCounter.counter = c; 1475 } 1476 constructor(c: number) { 1477 this.id = nextId++; 1478 this.counter = c; 1479 this.subCounter = new SubCounter(c); 1480 } 1481} 1482 1483@Component 1484struct SubCounterComp { 1485 @ObjectLink subValue: SubCounter; 1486 build() { 1487 Text(`SubCounterComp: this.subValue.counter: ${this.subValue.counter}`) 1488 .onClick(() => { 1489 // 2nd click handler 1490 this.subValue.counter = 7; 1491 }) 1492 } 1493} 1494@Component 1495struct CounterComp { 1496 @Prop value: ParentCounter; 1497 build() { 1498 Column({ space: 10 }) { 1499 Text(`this.value.incrCounter(): this.value.counter: ${this.value.counter}`) 1500 .fontSize(20) 1501 .onClick(() => { 1502 // 1st click handler 1503 this.value.incrCounter(); 1504 }) 1505 SubCounterComp({ subValue: this.value.subCounter }) 1506 Text(`this.value.incrSubCounter()`) 1507 .onClick(() => { 1508 // 3rd click handler 1509 this.value.incrSubCounter(77); 1510 }) 1511 Divider().height(2) 1512 } 1513 } 1514} 1515@Entry 1516@Component 1517struct ParentComp { 1518 @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1519 build() { 1520 Row() { 1521 Column() { 1522 CounterComp({ value: this.counter[0] }) 1523 CounterComp({ value: this.counter[1] }) 1524 CounterComp({ value: this.counter[2] }) 1525 Divider().height(5) 1526 ForEach(this.counter, 1527 (item: ParentCounter) => { 1528 CounterComp({ value: item }) 1529 }, 1530 (item: ParentCounter) => item.id.toString() 1531 ) 1532 Divider().height(5) 1533 Text('Parent: reset entire counter') 1534 .fontSize(20).height(50) 1535 .onClick(() => { 1536 this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1537 }) 1538 Text('Parent: incr counter[0].counter') 1539 .fontSize(20).height(50) 1540 .onClick(() => { 1541 this.counter[0].incrCounter(); 1542 this.counter[0].incrSubCounter(10); 1543 }) 1544 Text('Parent: set.counter to 10') 1545 .fontSize(20).height(50) 1546 .onClick(() => { 1547 this.counter[0].setSubCounter(10); 1548 }) 1549 } 1550 } 1551 } 1552} 1553``` 1554 1555 1556拷贝关系图示如下: 1557 1558 1559 1560 1561### 在@Observed装饰类的构造函数中延时更改成员变量 1562 1563在状态管理中,使用@Observed装饰类后,会给该类使用一层“代理”进行包装。当在组件中改变该类的成员变量时,会被该代理进行拦截,在更改数据源中值的同时,也会将变化通知给绑定的组件,从而实现观测变化与触发刷新。当开发者在类的构造函数中对成员变量进行赋值或者修改时,此修改不会经过代理(因为是直接对数据源中的值进行修改),也就无法被观测到。所以,如果开发者在类的构造函数中使用定时器修改类中的成员变量,即使该修改成功执行了,也不会触发UI的刷新。 1564 1565【反例】 1566 1567```ts 1568@Observed 1569class RenderClass { 1570 waitToRender: boolean = false; 1571 1572 constructor() { 1573 setTimeout(() => { 1574 this.waitToRender = true; 1575 console.log("change waitToRender to " + this.waitToRender); 1576 }, 1000) 1577 } 1578} 1579 1580@Entry 1581@Component 1582struct Index { 1583 @State @Watch('renderClassChange') renderClass: RenderClass = new RenderClass(); 1584 @State textColor: Color = Color.Black; 1585 1586 renderClassChange() { 1587 console.log("Render Class Change waitToRender is " + this.renderClass.waitToRender); 1588 } 1589 1590 build() { 1591 Row() { 1592 Column() { 1593 Text("Render Class waitToRender is " + this.renderClass.waitToRender) 1594 .fontSize(20) 1595 .fontColor(this.textColor) 1596 Button("Show") 1597 .onClick(() => { 1598 // 使用其他状态变量强行刷新UI的做法并不推荐,此处仅用来检测waitToRender的值是否更新 1599 this.textColor = Color.Red; 1600 }) 1601 } 1602 .width('100%') 1603 } 1604 .height('100%') 1605 } 1606} 1607``` 1608 1609上文的示例代码中在RenderClass的构造函数中使用定时器在1秒后修改了waitToRender的值,但是不会触发UI的刷新。此时点击按钮,强行刷新Text组件可以看到waitToRender的值已经被修改成了true。 1610 1611【正例】 1612 1613```ts 1614@Observed 1615class RenderClass { 1616 waitToRender: boolean = false; 1617 1618 constructor() { 1619 } 1620} 1621 1622@Entry 1623@Component 1624struct Index { 1625 @State @Watch('renderClassChange') renderClass: RenderClass = new RenderClass(); 1626 1627 renderClassChange() { 1628 console.log("Render Class Change waitToRender is " + this.renderClass.waitToRender); 1629 } 1630 1631 onPageShow() { 1632 setTimeout(() => { 1633 this.renderClass.waitToRender = true; 1634 console.log("change waitToRender to " + this.renderClass.waitToRender); 1635 }, 1000) 1636 } 1637 1638 build() { 1639 Row() { 1640 Column() { 1641 Text("Render Class Wait To Render is " + this.renderClass.waitToRender) 1642 .fontSize(20) 1643 } 1644 .width('100%') 1645 } 1646 .height('100%') 1647 } 1648} 1649``` 1650 1651上文的示例代码将定时器修改移入到组件内,此时界面显示时会先显示“Render Class Change waitToRender is false”。待定时器触发时,界面刷新显示“Render Class Change waitToRender is true”。 1652 1653因此,更推荐开发者在组件中对@Observed装饰的类成员变量进行修改实现刷新。 1654 1655### 在@Observed装饰的类内使用static方法进行初始化 1656 1657在@Observed装饰的类内,尽量避免使用static方法进行初始化,在创建时会绕过Observed的实现,导致无法被代理,UI不刷新。 1658 1659```ts 1660@Entry 1661@Component 1662struct MainPage { 1663 @State viewModel: ViewModel = ViewModel.build(); 1664 1665 build() { 1666 Column() { 1667 Button("Click") 1668 .onClick((event) => { 1669 this.viewModel.subViewModel.isShow = !this.viewModel.subViewModel.isShow; 1670 }) 1671 SubComponent({ viewModel: this.viewModel.subViewModel }) 1672 } 1673 .padding({ top: 60 }) 1674 .width('100%') 1675 .alignItems(HorizontalAlign.Center) 1676 } 1677} 1678 1679@Component 1680struct SubComponent { 1681 @ObjectLink viewModel: SubViewModel; 1682 1683 build() { 1684 Column() { 1685 if (this.viewModel.isShow) { 1686 Text("click to take effect"); 1687 } 1688 } 1689 } 1690} 1691 1692class ViewModel { 1693 subViewModel: SubViewModel = SubViewModel.build(); //内部静态方法创建 1694 1695 static build() { 1696 console.log("ViewModel build()") 1697 return new ViewModel(); 1698 } 1699} 1700 1701@Observed 1702class SubViewModel { 1703 isShow?: boolean = false; 1704 1705 static build() { 1706 //只有在SubViewModel内部的静态方法创建对象,会影响关联 1707 console.log("SubViewModel build()") 1708 let viewModel = new SubViewModel(); 1709 return viewModel; 1710 } 1711} 1712``` 1713 1714上文的示例中,在自定义组件ViewModel中使用static方法进行初始化,此时点击Click按钮,页面中并不会显示click to take effect。 1715 1716因此,不推荐开发者在自定义的类装饰器内使用static方法进行初始化。 1717