• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/>-&nbsp;类型必须是\@Observed装饰的class。<br/>-&nbsp;初始化的数值需要是数组项,或者class的属性。<br/>-&nbsp;同步源的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![zh-cn_image_0000001502255262](figures/zh-cn_image_0000001502255262.png)
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![Observed_ObjectLink_inheritance_object](figures/Observed_ObjectLink_inheritance_object.gif)
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![Observed_ObjectLink_nested_object](figures/Observed_ObjectLink_nested_object.gif)
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![Observed_ObjectLink_object_array](figures/Observed_ObjectLink_object_array.gif)
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![Observed_ObjectLink_2D_array](figures/Observed_ObjectLink_2D_array.gif)
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![Observed_ObjectLink_inherit_map](figures/Observed_ObjectLink_inherit_map.gif)
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![Observed_ObjectLink_inherit_set](figures/Observed_ObjectLink_inherit_set.gif)
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![ObjectLink-support-union-types](figures/ObjectLink-support-union-types.gif)
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.cousinIdthis.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![zh-cn_image_0000001653949465](figures/zh-cn_image_0000001653949465.jpg)
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