• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/>-&nbsp;类型必须是\@Observed装饰的class。<br/>-&nbsp;初始化的数值需要是数组项,或者class的属性。<br/>-&nbsp;同步源的class或者数组必须是\@State,\@Link,\@Provide,\@Consume或者\@ObjectLink装饰的数据。<br/>同步源是数组项的示例请参考[对象数组](#对象数组)。初始化的class的示例请参考[嵌套对象](#嵌套对象)。 |
67| 与源对象同步            | 双向。                                      |
68| 可以初始化子组件          | 允许,可用于初始化常规变量、\@State、\@Link、\@Prop、\@Provide |
69
70
71  **图1** 初始化规则图示  
72
73
74![zh-cn_image_0000001502255262](figures/zh-cn_image_0000001502255262.png)
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&lt;String&gt; {},并创建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.bthis.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.subValuethis.subValue都是同一个对象的不同引用,所以在点击CounterComp的click handler,改变this.value.subCounter.counterthis.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![zh-cn_image_0000001651665921](figures/zh-cn_image_0000001651665921.png)
1403
1404【反例】
1405
1406如果用\@Prop替代\@ObjectLink。点击第一个click handler,UI刷新正常。但是点击第二个onClick事件,\@Prop 对变量做了一个本地拷贝,CounterComp的第一个Text并不会刷新。
1407
1408  this.value.subCounterthis.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![zh-cn_image_0000001602146116](figures/zh-cn_image_0000001602146116.png)
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![zh-cn_image_0000001653949465](figures/zh-cn_image_0000001653949465.png)
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