1# \@Monitor装饰器:状态变量修改监听
2
3为了增强状态管理框架对状态变量变化的监听能力,开发者可以使用\@Monitor装饰器对状态变量进行监听。
4
5
6\@Monitor提供了对V2状态变量的监听。在阅读本文档前,建议提前阅读:[\@ComponentV2](./arkts-new-componentV2.md),[\@ObservedV2和\@Trace](./arkts-new-observedV2-and-trace.md),[\@Local](./arkts-new-local.md)。
7
8>**说明:**
9>
10>\@Monitor装饰器从API version 12开始支持。
11>
12
13## 概述
14
15\@Monitor装饰器用于监听状态变量修改,使得状态变量具有深度监听的能力:
16
17- \@Monitor装饰器支持在\@ComponentV2装饰的自定义组件中使用,未被状态变量装饰器[\@Local](arkts-new-local.md)、[\@Param](arkts-new-param.md)、[\@Provider](arkts-new-Provider-and-Consumer.md)、[\@Comsumer](arkts-new-Provider-and-Consumer.md)、[\@Computed](arkts-new-Computed.md)装饰的变量无法被\@Monitor监听到变化。
18
19- \@Monitor装饰器支持在类中与[\@ObservedV2、\@Trace](arkts-new-observedV2-and-trace.md)配合使用,不允许在未被\@ObservedV2装饰的类中使用\@Monitor装饰器。未被\@Trace装饰的属性无法被\@Monitor监听到变化。
20- 当观测的属性变化时,\@Monitor装饰器定义的回调方法将被调用。判断属性是否变化使用的是严格相等(===),当严格相等为false的情况下,就会触发\@Monitor的回调。当在一次事件中多次改变同一个属性时,将会使用初始值和最终值进行比较以判断是否变化。
21- 单个\@Monitor装饰器能够同时监听多个属性的变化,当这些属性在一次事件中共同变化时,只会触发一次\@Monitor的回调方法。
22- \@Monitor装饰器具有深度监听的能力,能够监听嵌套类、多维数组、对象数组中指定项的变化。对于嵌套类、对象数组中成员属性变化的监听要求该类被\@ObservedV2装饰且该属性被\@Trace装饰。
23- 在继承类场景中,可以在父子组件中对同一个属性分别定义\@Monitor进行监听,当属性变化时,父子组件中定义的\@Monitor回调均会被调用。
24- 和[\@Watch装饰器](arkts-watch.md)类似,开发者需要自己定义回调函数,区别在于\@Watch装饰器将函数名作为参数,而\@Monitor直接装饰回调函数。\@Monitor与\@Watch的对比可以查看[\@Monitor与\@Watch的对比](#monitor与watch对比)。
25
26## 状态管理V1版本\@Watch装饰器的局限性
27
28现有状态管理V1版本无法实现对对象、数组中某一单个属性或数组项变化的监听,且无法获取变化之前的值。
29
30```ts
31@Observed
32class Info {
33  name: string = "Tom";
34  age: number = 25;
35}
36@Entry
37@Component
38struct Index {
39  @State @Watch('onInfoChange') info: Info = new Info();
40  @State @Watch('onNumArrChange') numArr: number[] = [1,2,3,4,5];
41
42  onInfoChange() {
43    console.log(`info after change name: ${this.info.name}, age: ${this.info.age} `);
44  }
45  onNumArrChange() {
46    console.log(`numArr after change ${JSON.stringify(this.numArr)}`);
47  }
48  build() {
49    Row() {
50      Column() {
51        Button("change info name")
52          .onClick(() => {
53            this.info.name = "Jack";
54          })
55        Button("change info age")
56          .onClick(() => {
57            this.info.age = 30;
58          })
59        Button("change numArr[2]")
60          .onClick(() => {
61            this.numArr[2] = 5;
62          })
63        Button("change numArr[3]")
64          .onClick(() => {
65            this.numArr[3] = 6;
66          })
67      }
68      .width('100%')
69    }
70    .height('100%')
71  }
72}
73```
74
75上述代码中,点击"change info name"更改info中的name属性或点击"change info age"更改age时,均会触发info注册的\@Watch回调。点击"change numArr[2]"更改numArr中的第3个元素或点击"change numArr[3]"更改第4个元素时,均会触发numArr注册的\@Watch回调。在这两个回调中,由于无法获取数据更改前的值,在业务逻辑更加复杂的场景下,无法准确知道是哪一个属性或元素发生了改变从而触发了\@Watch事件,这不便于开发者对变量的更改进行准确监听。因此推出\@Monitor装饰器实现对对象、数组中某一单个属性或数组项变化的监听,并且能够获取到变化之前的值。
76
77## 装饰器说明
78
79| \@Monitor属性装饰器 | 说明                                                         |
80| ------------------- | ------------------------------------------------------------ |
81| 装饰器参数          | 字符串类型的对象属性名。可同时监听多个对象属性,每个属性以逗号隔开,例如@Monitor("prop1", "prop2")。可监听深层的属性变化,如多维数组中的某一个元素,嵌套对象或对象数组中的某一个属性。详见[监听变化](#监听变化)。 |
82| 装饰对象            | \@Monitor装饰成员方法。当监听的属性发生变化时,会触发该回调方法。该回调方法以[IMonitor类型](#imonitor类型)的变量作为参数,开发者可以从该参数中获取变化前后的相关信息。 |
83
84## 接口说明
85
86### IMonitor类型
87
88IMonitor类型的变量用作\@Monitor装饰方法的参数。
89
90| 属性       | 类型            | 参数          | 返回值             | 说明                                                         |
91| ---------- | --------------- | ------------- | ------------------ | ------------------------------------------------------------ |
92| dirty      | Array\<string\> | 无            | 无                 | 保存发生变化的属性名。                                       |
93| value\<T\> | function        | path?: string | IMonitorValue\<T\> | 获得指定属性(path)的变化信息。当不填path时返回@Monitor监听顺序中第一个改变的属性的变化信息。 |
94
95### IMonitorValue\<T\>类型
96
97IMonitorValue\<T\>类型保存了属性变化的信息,包括属性名、变化前值、当前值。
98
99| 属性   | 类型   | 说明                       |
100| ------ | ------ | -------------------------- |
101| before | T      | 监听属性变化之前的值。     |
102| now    | T      | 监听属性变化之后的当前值。 |
103| path   | string | 监听的属性名。             |
104
105## 监听变化
106
107### 在\@ComponentV2装饰的自定义组件中使用\@Monitor
108
109使用\@Monitor监听的状态变量发生变化时,会触发\@Monitor的回调方法。
110
111- \@Monitor监听的变量需要被\@Local、\@Param、\@Provider、\@Consumer、\@Computed装饰,未被状态变量装饰器装饰的变量在变化时无法被监听。\@Monitor可以同时监听多个状态变量,这些变量名之间用","隔开。
112
113  ```ts
114  @Entry
115  @ComponentV2
116  struct Index {
117    @Local message: string = "Hello World";
118    @Local name: string = "Tom";
119    @Local age: number = 24;
120    @Monitor("message", "name")
121    onStrChange(monitor: IMonitor) {
122      monitor.dirty.forEach((path: string) => {
123        console.log(`${path} changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`)
124      })
125    }
126    build() {
127      Column() {
128        Button("change string")
129          .onClick(() => {
130            this.message += "!";
131            this.name = "Jack";
132        })
133      }
134    }
135  }
136  ```
137
138- \@Monitor监听的状态变量为类对象时,仅能监听对象整体的变化。监听类属性的变化需要类属性被\@Trace装饰。
139
140  ```ts
141  class Info {
142    name: string;
143    age: number;
144    constructor(name: string, age: number) {
145      this.name = name;
146      this.age = age;
147    }
148  }
149  @Entry
150  @ComponentV2
151  struct Index {
152    @Local info: Info = new Info("Tom", 25);
153    @Monitor("info")
154    infoChange(monitor: IMonitor) {
155      console.log(`info change`);
156    }
157    @Monitor("info.name")
158    infoPropertyChange(monitor: IMonitor) {
159      console.log(`info name change`);
160    }
161    build() {
162      Column() {
163        Text(`name: ${this.info.name}, age: ${this.info.age}`)
164        Button("change info")
165          .onClick(() => {
166            this.info = new Info("Lucy", 18); // 能够监听到
167          })
168        Button("change info.name")
169          .onClick(() => {
170            this.info.name = "Jack"; // 监听不到
171          })
172      }
173    }
174  }
175  ```
176
177### 在\@ObservedV2装饰的类中使用\@Monitor
178
179使用\@Monitor监听的属性发生变化时,会触发\@Monitor的回调方法。
180
181- \@Monitor监听的对象属性需要被\@Trace装饰,未被\@Trace装饰的属性的变化无法被监听。\@Monitor可以同时监听多个属性,这些属性之间用","隔开。
182
183```ts
184@ObservedV2
185class Info {
186  @Trace name: string = "Tom";
187  @Trace region: string = "North";
188  @Trace job: string = "Teacher";
189  age: number = 25;
190  // name被@Trace装饰,能够监听变化
191  @Monitor("name")
192  onNameChange(monitor: IMonitor) {
193    console.log(`name change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
194  }
195  // age未被@Trace装饰,不能监听变化
196  @Monitor("age")
197  onAgeChange(monitor: IMonitor) {
198    console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
199  }
200  // region与job均被@Trace装饰,能够监听变化
201  @Monitor("region", "job")
202  onChange(monitor: IMonitor) {
203    monitor.dirty.forEach((path: string) => {
204      console.log(`${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
205    })
206  }
207}
208@Entry
209@ComponentV2
210struct Index {
211  info: Info = new Info();
212  build() {
213    Column() {
214      Button("change name")
215        .onClick(() => {
216          this.info.name = "Jack"; // 能够触发onNameChange方法
217        })
218      Button("change age")
219        .onClick(() => {
220          this.info.age = 26; // 不能够触发onAgeChange方法
221        })
222      Button("change region")
223        .onClick(() => {
224          this.info.region = "South"; // 能够触发onChange方法
225        })
226      Button("change job")
227        .onClick(() => {
228          this.info.job = "Driver"; // 能够触发onChange方法
229        })
230    }
231  }
232}
233```
234
235- \@Monitor可以监听深层属性的变化,该深层属性需要被@Trace装饰。
236
237```ts
238@ObservedV2
239class Inner {
240  @Trace num: number = 0;
241}
242@ObservedV2
243class Outer {
244  inner: Inner = new Inner();
245  @Monitor("inner.num")
246  onChange(monitor: IMonitor) {
247    console.log(`inner.num change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
248  }
249}
250@Entry
251@ComponentV2
252struct Index {
253  outer: Outer = new Outer();
254  build() {
255    Column() {
256      Button("change name")
257        .onClick(() => {
258          this.outer.inner.num = 100; // 能够触发onChange方法
259        })
260    }
261  }
262}
263```
264
265- 在继承类场景下,可以在继承链中对同一个属性进行多次监听。
266
267```ts
268@ObservedV2
269class Base {
270  @Trace name: string;
271  // 基类监听name属性
272  @Monitor("name")
273  onBaseNameChange(monitor: IMonitor) {
274    console.log(`Base Class name change`);
275  }
276  constructor(name: string) {
277    this.name = name;
278  }
279}
280@ObservedV2
281class Derived extends Base {
282  // 继承类监听name属性
283  @Monitor("name")
284  onDerivedNameChange(monitor: IMonitor) {
285    console.log(`Derived Class name change`);
286  }
287  constructor(name: string) {
288    super(name);
289  }
290}
291@Entry
292@ComponentV2
293struct Index {
294  derived: Derived = new Derived("AAA");
295  build() {
296    Column() {
297      Button("change name")
298        .onClick(() => {
299          this.derived.name = "BBB"; // 能够先后触发onBaseNameChange、onDerivedNameChange方法
300        })
301    }
302  }
303}
304```
305
306### 通用监听能力
307
308\@Monitor还有一些通用的监听能力。
309
310- \@Monitor支持对数组中的项进行监听,包括多维数组,对象数组。\@Monitor无法监听内置类型(Array、Map、Date、Set)的API调用引起的变化。当\@Monitor监听数组整体时,只能观测到数组整体的赋值。可以通过监听数组的长度变化来判断数组是否有插入、删除等变化。当前仅支持使用"."的方式表达深层属性、数组项的监听。
311
312```ts
313@ObservedV2
314class Info {
315  @Trace name: string;
316  @Trace age: number;
317
318  constructor(name: string, age: number) {
319    this.name = name;
320    this.age = age;
321  }
322}
323@ObservedV2
324class ArrMonitor {
325  @Trace dimensionTwo: number[][] = [[1,1,1],[2,2,2],[3,3,3]];
326  @Trace dimensionThree: number[][][] = [[[1],[2],[3]],[[4],[5],[6]],[[7],[8],[9]]];
327  @Trace infoArr: Info[] = [new Info("Jack", 24), new Info("Lucy", 18)];
328  // dimensionTwo为二维简单类型数组,且被@Trace装饰,能够观测里面的元素变化
329  @Monitor("dimensionTwo.0.0", "dimensionTwo.1.1")
330  onDimensionTwoChange(monitor: IMonitor) {
331    monitor.dirty.forEach((path: string) => {
332      console.log(`dimensionTwo path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
333    })
334  }
335  // dimensionThree为三维简单类型数组,且被@Trace装饰,能够观测里面的元素变化
336  @Monitor("dimensionThree.0.0.0", "dimensionThree.1.1.0")
337  onDimensionThreeChange(monitor: IMonitor) {
338    monitor.dirty.forEach((path: string) => {
339      console.log(`dimensionThree path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
340    })
341  }
342  // Info类中属性name、age均被@Trace装饰,能够监听到变化
343  @Monitor("infoArr.0.name", "infoArr.1.age")
344  onInfoArrPropertyChange(monitor: IMonitor) {
345    monitor.dirty.forEach((path: string) => {
346      console.log(`infoArr path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
347    })
348  }
349  // infoArr被@Trace装饰,能够监听到infoArr整体赋值的变化
350  @Monitor("infoArr")
351  onInfoArrChange(monitor: IMonitor) {
352    console.log(`infoArr whole change`);
353  }
354  // 能够监听到infoArr的长度变化
355  @Monitor("infoArr.length")
356  onInfoArrLengthChange(monitor: IMonitor) {
357    console.log(`infoArr length change`);
358  }
359}
360@Entry
361@ComponentV2
362struct Index {
363  arrMonitor: ArrMonitor = new ArrMonitor();
364  build() {
365    Column() {
366      Button("Change dimensionTwo")
367        .onClick(() => {
368          // 能够触发onDimensionTwoChange方法
369          this.arrMonitor.dimensionTwo[0][0]++;
370          this.arrMonitor.dimensionTwo[1][1]++;
371        })
372      Button("Change dimensionThree")
373        .onClick(() => {
374          // 能够触发onDimensionThreeChange方法
375          this.arrMonitor.dimensionThree[0][0][0]++;
376          this.arrMonitor.dimensionThree[1][1][0]++;
377        })
378      Button("Change info property")
379        .onClick(() => {
380          // 能够触发onInfoArrPropertyChange方法
381          this.arrMonitor.infoArr[0].name = "Tom";
382          this.arrMonitor.infoArr[1].age = 19;
383        })
384      Button("Change whole infoArr")
385        .onClick(() => {
386          // 能够触发onInfoArrChange、onInfoArrPropertyChange、onInfoArrLengthChange方法
387          this.arrMonitor.infoArr = [new Info("Cindy", 8)];
388        })
389      Button("Push new info to infoArr")
390        .onClick(() => {
391          // 能够触发onInfoArrPropertyChange、onInfoArrLengthChange方法
392          this.arrMonitor.infoArr.push(new Info("David", 50));
393        })
394    }
395  }
396}
397```
398
399- 对象整体改变,但监听的属性不变时,不触发\@Monitor回调。
400
401下面的示例按照Step1-Step2-Step3的顺序点击,表现为代码注释中的行为。
402
403如果只点击Step2或Step3,改变name、age的值,此时会触发onNameChange和onAgeChange方法。
404
405```ts
406@ObservedV2
407class Info {
408  @Trace person: Person;
409  @Monitor("person.name")
410  onNameChange(monitor: IMonitor) {
411    console.log(`name change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
412  }
413  @Monitor("person.age")
414  onAgeChange(monitor: IMonitor) {
415    console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
416  }
417  constructor(name: string, age: number) {
418    this.person = new Person(name, age);
419  }
420}
421@ObservedV2
422class Person {
423  @Trace name: string;
424  @Trace age: number;
425  constructor(name: string, age: number) {
426    this.name = name;
427    this.age = age;
428  }
429}
430@Entry
431@ComponentV2
432struct Index {
433  info: Info = new Info("Tom", 25);
434  build() {
435    Column() {
436      Button("Step1、Only change name")
437        .onClick(() => {
438          this.info.person = new Person("Jack", 25);  // 能够触发onNameChange方法,不触发onAgeChange方法
439        })
440      Button("Step2、Only change age")
441        .onClick(() => {
442          this.info.person = new Person("Jack", 18);  // 能够触发onAgeChange方法,不触发onNameChange方法
443        })
444      Button("Step3、Change name and age")
445        .onClick(() => {
446          this.info.person = new Person("Lucy", 19);  // 能够触发onNameChange、onAgeChange方法
447        })
448    }
449  }
450}
451```
452
453- 在一次事件中多次改变被\@Monitor监听的属性,以最后一次修改为准。
454
455```ts
456@ObservedV2
457class Frequence {
458  @Trace count: number = 0;
459  @Monitor("count")
460  onCountChange(monitor: IMonitor) {
461    console.log(`count change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
462  }
463}
464@Entry
465@ComponentV2
466struct Index {
467  frequence: Frequence = new Frequence();
468  build() {
469    Column() {
470      Button("change count to 1000")
471        .onClick(() => {
472          for (let i = 1; i <= 1000; i++) {
473            this.frequence.count = i;
474          }
475        })
476      Button("change count to 0 then to 1000")
477        .onClick(() => {
478          for (let i = 999; i >= 0; i--) {
479            this.frequence.count = i;
480          }
481          this.frequence.count = 1000; // 最终不触发onCountChange方法
482        })
483    }
484  }
485}
486```
487
488在点击按钮"change count to 1000"后,会触发一次onCountChange方法,并输出日志"count change from 0 to 1000"。在点击按钮"change count to 0 then to 1000"后,由于事件前后属性count的值并没有改变,都为1000,所以不触发onCountChange方法。
489
490## 限制条件
491
492使用\@Monitor需要注意如下限制条件:
493
494- 不建议在一个类中对同一个属性进行多次\@Monitor的监听。当一个类中存在对一个属性的多次监听时,只有最后一个定义的监听方法会生效。
495
496```ts
497@ObservedV2
498class Info {
499  @Trace name: string = "Tom";
500  @Monitor("name")
501  onNameChange(monitor: IMonitor) {
502    console.log(`onNameChange`);
503  }
504  @Monitor("name")
505  onNameChangeDuplicate(monitor: IMonitor) {
506    console.log(`onNameChangeDuplicate`);
507  }
508}
509@Entry
510@ComponentV2
511struct Index {
512  info: Info = new Info();
513  build() {
514    Column() {
515      Button("change name")
516        .onClick(() => {
517          this.info.name = "Jack"; // 仅会触发onNameChangeDuplicate方法
518        })
519    }
520  }
521}
522```
523
524- \@Monitor的参数需要为监听属性名的字符串,仅可以使用字符串字面量、const常量、enum枚举值作为参数。如果使用变量作为参数,仅会监听\@Monitor初始化时,变量值所对应的属性。当更改变量时,\@Monitor无法实时改变监听的属性,即\@Monitor监听的目标属性从初始化时便已经确定,无法动态更改。不建议开发者使用变量作为\@Monitor的参数进行初始化。
525
526```ts
527const t2: string = "t2"; // const常量
528enum ENUM {
529  T3 = "t3" // enum枚举值
530};
531let t4: string = "t4"; // 变量
532@ObservedV2
533class Info {
534  @Trace t1: number = 0;
535  @Trace t2: number = 0;
536  @Trace t3: number = 0;
537  @Trace t4: number = 0;
538  @Trace t5: number = 0;
539  @Monitor("t1") // 字符串字面量
540  onT1Change(monitor: IMonitor) {
541    console.log(`t1 change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
542  }
543  @Monitor(t2)
544  onT2Change(monitor: IMonitor) {
545    console.log(`t2 change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
546  }
547  @Monitor(ENUM.T3)
548  onT3Change(monitor: IMonitor) {
549    console.log(`t3 change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
550  }
551  @Monitor(t4)
552  onT4Change(monitor: IMonitor) {
553    console.log(`t4 change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
554  }
555}
556@Entry
557@ComponentV2
558struct Index {
559  info: Info = new Info();
560  build() {
561    Column() {
562      Button("Change t1")
563        .onClick(() => {
564          this.info.t1++; // 能够触发onT1Change方法
565        })
566      Button("Change t2")
567        .onClick(() => {
568          this.info.t2++; // 能够触发onT2Change方法
569        })
570      Button("Change t3")
571        .onClick(() => {
572          this.info.t3++; // 能够触发onT3Change方法
573        })
574      Button("Change t4")
575        .onClick(() => {
576          this.info.t4++; // 能够触发onT4Change方法
577        })
578      Button("Change var t4 to t5")
579        .onClick(() => {
580          t4 = "t5"; // 更改变量值为"t5"
581        })
582      Button("Change t5")
583        .onClick(() => {
584          this.info.t5++; // onT4Change仍监听t4,不会触发
585        })
586      Button("Change t4 again")
587        .onClick(() => {
588          this.info.t4++; // 能够触发onT4Change方法
589        })
590    }
591  }
592}
593```
594
595- 建议开发者避免在\@Monitor中再次更改被监听的属性,这会导致无限循环。
596
597```ts
598@ObservedV2
599class Info {
600  @Trace count: number = 0;
601  @Monitor("count")
602  onCountChange(monitor: IMonitor) {
603    this.count++; // 应避免这种写法,会导致无限循环
604  }
605}
606```
607
608## \@Monitor与\@Watch对比
609
610\@Monitor与\@Watch的用法、功能对比如下:
611
612|                    | \@Watch                                 | \@Monitor                                                    |
613| ------------------ | --------------------------------------- | ------------------------------------------------------------ |
614| 参数               | 回调方法名                              | 监听状态变量名、属性名                                       |
615| 监听目标数         | 只能监听单个状态变量                    | 能同时监听多个状态变量                                       |
616| 监听能力           | 跟随状态变量观察能力(一层)            | 跟随状态变量观察能力(深层)                                 |
617| 能否获取变化前的值 | 不能获取变化前的值                      | 能获取变化前的值                                             |
618| 监听条件           | 监听对象为状态变量                      | 监听对象为状态变量或为\@Trace装饰的类成员属性                |
619| 使用限制           | 仅能在\@Component装饰的自定义组件中使用 | 能在\@ComponentV2装饰的自定义组件中使用,也能在\@ObservedV2装饰的类中使用 |
620
621## 使用场景
622
623### 监听深层属性变化
624
625\@Monitor可以监听深层属性的变化,并能够根据更改前后的值做分类处理。
626
627下面的示例中监听了属性value的变化,并根据变化的幅度改变Text组件显示的样式。
628
629```ts
630@ObservedV2
631class Info {
632  @Trace value: number = 50;
633}
634@ObservedV2
635class UIStyle {
636  info: Info = new Info();
637  @Trace color: Color = Color.Black;
638  @Trace fontSize: number = 45;
639  @Monitor("info.value")
640  onValueChange(monitor: IMonitor) {
641    let lastValue: number = monitor.value()?.before as number;
642    let curValue: number = monitor.value()?.now as number;
643    if (lastValue != 0) {
644      let diffPercent: number = (curValue - lastValue) / lastValue;
645      if (diffPercent > 0.1) {
646        this.color = Color.Red;
647        this.fontSize = 50;
648      } else if (diffPercent < -0.1) {
649        this.color = Color.Green;
650        this.fontSize = 40;
651      } else {
652        this.color = Color.Black;
653        this.fontSize = 45;
654      }
655    }
656  }
657}
658@Entry
659@ComponentV2
660struct Index {
661  textStyle: UIStyle = new UIStyle();
662  build() {
663    Column() {
664      Text(`Important Value: ${this.textStyle.info.value}`)
665        .fontColor(this.textStyle.color)
666        .fontSize(this.textStyle.fontSize)
667      Button("change!")
668        .onClick(() => {
669          this.textStyle.info.value = Math.floor(Math.random() * 100) + 1;
670        })
671    }
672  }
673}
674```
675
676## 常见问题
677
678### 自定义组件中\@Monitor对变量监听的生效及失效时间
679
680当\@Monitor定义在\@ComponentV2装饰的自定义组件中时,\@Monitor会在状态变量初始化完成之后生效,并在组件销毁时失效。
681
682```ts
683@ObservedV2
684class Info {
685  @Trace message: string = "not initialized";
686
687  constructor() {
688    console.log("in constructor message change to initialized");
689    this.message = "initialized";
690  }
691}
692@ComponentV2
693struct Child {
694  @Param info: Info = new Info();
695  @Monitor("info.message")
696  onMessageChange(monitor: IMonitor) {
697    console.log(`Child message change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
698  }
699  aboutToAppear(): void {
700    this.info.message = "Child aboutToAppear";
701  }
702  aboutToDisappear(): void {
703    console.log("Child aboutToDisappear");
704    this.info.message = "Child aboutToDisappear";
705  }
706  build() {
707    Column() {
708      Text("Child")
709      Button("change message in Child")
710        .onClick(() => {
711          this.info.message = "Child click to change Message";
712        })
713    }
714    .borderColor(Color.Red)
715    .borderWidth(2)
716
717  }
718}
719@Entry
720@ComponentV2
721struct Index {
722  @Local info: Info = new Info();
723  @Local flag: boolean = false;
724  @Monitor("info.message")
725  onMessageChange(monitor: IMonitor) {
726    console.log(`Index message change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
727  }
728
729  build() {
730    Column() {
731      Button("show/hide Child")
732        .onClick(() => {
733          this.flag = !this.flag
734        })
735      Button("change message in Index")
736        .onClick(() => {
737          this.info.message = "Index click to change Message";
738        })
739      if (this.flag) {
740        Child({ info: this.info })
741      }
742    }
743  }
744}
745```
746
747在上面的例子中,可以通过创建和销毁Child组件来观察定义在自定义组件中的\@Monitor的生效和失效时机。推荐按如下顺序进行操作:
748
749- 当Index组件创建Info类实例时,日志输出`in constructor message change to initialized`。此时Index组件的\@Monitor还未初始化成功,因此不会监听到message的变化。
750- 当Index组件创建完成,页面加载完成后,点击按钮“change message in Index”,此时Index组件中的\@Monitor能够监听到变化,日志输出`Index message change from initialized to Index click to change Message`。
751- 点击按钮“show/hide Child”,创建Child组件,在Child组件初始化\@Param装饰的变量以及\@Monitor之后,调用Child组件的aboutToAppear回调,改变message。此时Index组件与Child组件的\@Monitor均能监听到变化,日志输出`Index message change from Index click to change Message to Child aboutToAppear`以及`Child message change from Index click to change Message to Child aboutToAppear`。
752- 点击按钮“change message in Child”,改变message。此时Index组件与Child组件的\@Monitor均能监听到变化,日志输出`Index message change from Child aboutToAppear to Child click to change Message`以及`Child message change from Child aboutToAppear to Child click to change Message`。
753- 点击按钮”show/hide Child“,销毁Child组件,调用Child组件的aboutToDisappear回调,改变message。此时Index组件与Child组件的\@Monitor均能监听到变化,日志输出`Child aboutToDisappear`,`Index message change from Child click to change Message to Child aboutToDisappear`以及`Child message change from Child click to change Message to Child aboutToDisappear`。
754- 点击按钮“change message in Index”,改变message。此时Child组件已销毁,其注册的\@Monitor监听也被解注册,仅有Index组件的\@Monitor能够监听到变化,日志输出`Index message change from Child aboutToDisappear to Index click to change Message`。
755
756这表明Child组件中定义的\@Monitor监听随着Child组件的创建初始化生效,随着Child组件的销毁失效。
757
758### 类中\@Monitor对变量监听的生效及失效时间
759
760当\@Monitor定义在\@ObservedV2装饰的类中时,\@Monitor会在类创建完成后生效,在类销毁时失效。
761
762```ts
763@ObservedV2
764class Info {
765  @Trace message: string = "not initialized";
766
767  constructor() {
768    this.message = "initialized";
769  }
770  @Monitor("message")
771  onMessageChange(monitor: IMonitor) {
772    console.log(`message change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
773  }
774}
775
776@Entry
777@ComponentV2
778struct Index {
779  info: Info = new Info();
780
781  aboutToAppear(): void {
782    this.info.message = "Index aboutToAppear";
783  }
784
785  build() {
786    Column() {
787      Button("change message")
788        .onClick(() => {
789          this.info.message = "Index click to change message";
790        })
791    }
792  }
793}
794```
795
796上面的例子中,\@Monitor会在info创建完成后生效,这个时机晚于类的constructor,早于自定义组件的aboutToAppear。当界面加载完成后,点击“change message”,修改message变量。此时日志输出信息如下:
797
798```ts
799message change from initialized to Index aboutToAppear
800message change from Index aboutToAppear to Index click to change message
801```
802
803类中定义的\@Monitor随着类的销毁失效。而由于类的实际销毁释放依赖于垃圾回收机制,因此会出现即使所在自定义组件已经销毁,类却还未及时销毁,导致类中定义的\@Monitor仍在监听变化的情况。
804
805```ts
806@ObservedV2
807class InfoWrapper {
808  info?: Info;
809  constructor(info: Info) {
810    this.info = info;
811  }
812  @Monitor("info.age")
813  onInfoAgeChange(monitor: IMonitor) {
814    console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`)
815  }
816}
817@ObservedV2
818class Info {
819  @Trace age: number;
820  constructor(age: number) {
821    this.age = age;
822  }
823}
824@ComponentV2
825struct Child {
826  @Param @Require infoWrapper: InfoWrapper;
827  aboutToDisappear(): void {
828    console.log("Child aboutToDisappear", this.infoWrapper.info?.age)
829  }
830  build() {
831    Column() {
832      Text(`${this.infoWrapper.info?.age}`)
833    }
834  }
835}
836@Entry
837@ComponentV2
838struct Index {
839  dataArray: Info[] = [];
840  @Local showFlag: boolean = true;
841  aboutToAppear(): void {
842    for (let i = 0; i < 5; i++) {
843      this.dataArray.push(new Info(i));
844    }
845  }
846  build() {
847    Column() {
848      Button("change showFlag")
849        .onClick(() => {
850          this.showFlag = !this.showFlag;
851        })
852      Button("change number")
853        .onClick(() => {
854          console.log("click to change age")
855          this.dataArray.forEach((info: Info) => {
856            info.age += 100;
857          })
858        })
859      if (this.showFlag) {
860        Column() {
861          Text("Childs")
862          ForEach(this.dataArray, (info: Info) => {
863            Child({ infoWrapper: new InfoWrapper(info) })
864          })
865        }
866        .borderColor(Color.Red)
867        .borderWidth(2)
868      }
869    }
870  }
871}
872```
873
874在上面的例子中,当点击“change showFlag”切换if组件的条件时,Child组件会被销毁。此时,点击“change number”修改age的值时,可以通过日志观察到InfoWrapper中定义的\@Monitor回调仍然被触发了。这是因为此时自定义组件Child虽然执行了aboutToDisappear,但是其成员变量infoWrapper还没有被立刻回收,当变量发生变化时,依然能够调用到infoWrapper中定义的onInfoAgeChange方法,所以从现象上看\@Monitor回调仍会被触发。
875
876借助垃圾回收机制去取消\@Monitor的监听是不稳定的,开发者可以采用以下两种方式去管理\@Monitor的失效时间:
877
8781、将\@Monitor定义在自定义组件中。由于自定义组件在销毁时,状态管理框架会手动取消\@Monitor的监听,因此在自定义组件调用完aboutToDisappear,尽管自定义组件的数据不一定已经被释放,但\@Monitor回调已不会再被触发。
879
880```ts
881@ObservedV2
882class InfoWrapper {
883  info?: Info;
884  constructor(info: Info) {
885    this.info = info;
886  }
887}
888@ObservedV2
889class Info {
890  @Trace age: number;
891  constructor(age: number) {
892    this.age = age;
893  }
894}
895@ComponentV2
896struct Child {
897  @Param @Require infoWrapper: InfoWrapper;
898  @Monitor("infoWrapper.info.age")
899  onInfoAgeChange(monitor: IMonitor) {
900    console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`)
901  }
902  aboutToDisappear(): void {
903    console.log("Child aboutToDisappear", this.infoWrapper.info?.age)
904  }
905  build() {
906    Column() {
907      Text(`${this.infoWrapper.info?.age}`)
908    }
909  }
910}
911@Entry
912@ComponentV2
913struct Index {
914  dataArray: Info[] = [];
915  @Local showFlag: boolean = true;
916  aboutToAppear(): void {
917    for (let i = 0; i < 5; i++) {
918      this.dataArray.push(new Info(i));
919    }
920  }
921  build() {
922    Column() {
923      Button("change showFlag")
924        .onClick(() => {
925          this.showFlag = !this.showFlag;
926        })
927      Button("change number")
928        .onClick(() => {
929          console.log("click to change age")
930          this.dataArray.forEach((info: Info) => {
931            info.age += 100;
932          })
933        })
934      if (this.showFlag) {
935        Column() {
936          Text("Childs")
937          ForEach(this.dataArray, (info: Info) => {
938            Child({ infoWrapper: new InfoWrapper(info) })
939          })
940        }
941        .borderColor(Color.Red)
942        .borderWidth(2)
943      }
944    }
945  }
946}
947```
948
9492、主动置空监听的对象。当自定义组件即将销毁时,主动置空\@Monitor的监听目标,这样\@Monitor无法再监听原监听目标的变化,达到取消\@Monitor监听的效果。
950
951```ts
952@ObservedV2
953class InfoWrapper {
954  info?: Info;
955  constructor(info: Info) {
956    this.info = info;
957  }
958  @Monitor("info.age")
959  onInfoAgeChange(monitor: IMonitor) {
960    console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`)
961  }
962}
963@ObservedV2
964class Info {
965  @Trace age: number;
966  constructor(age: number) {
967    this.age = age;
968  }
969}
970@ComponentV2
971struct Child {
972  @Param @Require infoWrapper: InfoWrapper;
973  aboutToDisappear(): void {
974    console.log("Child aboutToDisappear", this.infoWrapper.info?.age)
975    this.infoWrapper.info = undefined; // 使InfoWrapper对info.age的监听失效
976  }
977  build() {
978    Column() {
979      Text(`${this.infoWrapper.info?.age}`)
980    }
981  }
982}
983@Entry
984@ComponentV2
985struct Index {
986  dataArray: Info[] = [];
987  @Local showFlag: boolean = true;
988  aboutToAppear(): void {
989    for (let i = 0; i < 5; i++) {
990      this.dataArray.push(new Info(i));
991    }
992  }
993  build() {
994    Column() {
995      Button("change showFlag")
996        .onClick(() => {
997          this.showFlag = !this.showFlag;
998        })
999      Button("change number")
1000        .onClick(() => {
1001          console.log("click to change age")
1002          this.dataArray.forEach((info: Info) => {
1003            info.age += 100;
1004          })
1005        })
1006      if (this.showFlag) {
1007        Column() {
1008          Text("Childs")
1009          ForEach(this.dataArray, (info: Info) => {
1010            Child({ infoWrapper: new InfoWrapper(info) })
1011          })
1012        }
1013        .borderColor(Color.Red)
1014        .borderWidth(2)
1015      }
1016    }
1017  }
1018}
1019```
1020
1021### 正确设置\@Monitor入参
1022
1023由于\@Monitor无法对入参做编译时校验,当前存在以下写法不符合\@Monitor监听条件但\@Monitor仍会触发的情况。开发者应当正确传入\@Monitor入参,不传入非状态变量,避免造成功能异常或行为表现不符合预期。
1024
1025【反例1】
1026
1027```ts
1028@ObservedV2
1029class Info {
1030  name: string = "John";
1031  @Trace age: number = 24;
1032  @Monitor("age", "name") // 同时监听状态变量age和非状态变量name
1033  onPropertyChange(monitor: IMonitor) {
1034    monitor.dirty.forEach((path: string) => {
1035      console.log(`property path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
1036    })
1037  }
1038}
1039@Entry
1040@ComponentV2
1041struct Index {
1042  info: Info = new Info();
1043  build() {
1044    Column() {
1045      Button("change age&name")
1046        .onClick(() => {
1047          this.info.age = 25; // 同时改变状态变量age和非状态变量name
1048          this.info.name = "Johny";
1049        })
1050    }
1051  }
1052}
1053```
1054
1055上面的代码中,当点击按钮同时更改状态变量age和非状态变量name时,会输出以下日志:
1056
1057```
1058property path:age change from 24 to 25
1059property path:name change from John to Johny
1060```
1061
1062实际上name属性本身并不是可被观测的变量,不应被加入到\@Monitor的入参当中。建议开发者去除对name属性的监听或者将给name加上\@Trace装饰成为状态变量。
1063
1064【正例1】
1065
1066```ts
1067@ObservedV2
1068class Info {
1069  name: string = "John";
1070  @Trace age: number = 24;
1071  @Monitor("age") // 仅监听状态变量age
1072  onPropertyChange(monitor: IMonitor) {
1073    monitor.dirty.forEach((path: string) => {
1074      console.log(`property path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
1075    })
1076  }
1077}
1078@Entry
1079@ComponentV2
1080struct Index {
1081  info: Info = new Info();
1082  build() {
1083    Column() {
1084      Button("change age&name")
1085        .onClick(() => {
1086          this.info.age = 25; // 状态变量age改变
1087          this.info.name = "Johny";
1088        })
1089    }
1090  }
1091}
1092```
1093
1094【反例2】
1095
1096```ts
1097@ObservedV2
1098class Info {
1099  name: string = "John";
1100  @Trace age: number = 24;
1101  get myAge() {
1102    return this.age; // age为状态变量
1103  }
1104  @Monitor("myAge") // 监听非@Computed装饰的getter访问器
1105  onPropertyChange() {
1106    console.log("age changed");
1107  }
1108}
1109@Entry
1110@ComponentV2
1111struct Index {
1112  info: Info = new Info();
1113  build() {
1114    Column() {
1115      Button("change age")
1116        .onClick(() => {
1117          this.info.age = 25; // 状态变量age改变
1118        })
1119    }
1120  }
1121}
1122```
1123
1124上面的代码中,\@Monitor的入参为一个getter访问器的名字,但该getter访问器本身并未被\@Computed装饰,不是一个可被监听的变量。但由于使用了状态变量参与了计算,在状态变量变化后,myAge也被认为发生了变化,因此触发了\@Monitor回调。建议开发者给myAge添加\@Computed装饰器或当getter访问器直接返回状态变量时,不监听getter访问器而是直接监听状态变量本身。
1125
1126【正例2】
1127
1128将myAge变为状态变量:
1129
1130```ts
1131@ObservedV2
1132class Info {
1133  name: string = "John";
1134  @Trace age: number = 24;
1135  @Computed // 给myAge添加@Computed成为状态变量
1136  get myAge() {
1137    return this.age;
1138  }
1139  @Monitor("myAge") // 监听@Computed装饰的getter访问器
1140  onPropertyChange() {
1141    console.log("age changed");
1142  }
1143}
1144@Entry
1145@ComponentV2
1146struct Index {
1147  info: Info = new Info();
1148  build() {
1149    Column() {
1150      Button("change age")
1151        .onClick(() => {
1152          this.info.age = 25; // 状态变量age改变
1153        })
1154    }
1155  }
1156}
1157```
1158
1159或直接监听状态变量本身:
1160
1161```ts
1162@ObservedV2
1163class Info {
1164  name: string = "John";
1165  @Trace age: number = 24;
1166  @Monitor("age") // 监听状态变量age
1167  onPropertyChange() {
1168    console.log("age changed");
1169  }
1170}
1171@Entry
1172@ComponentV2
1173struct Index {
1174  info: Info = new Info();
1175  build() {
1176    Column() {
1177      Button("change age")
1178        .onClick(() => {
1179          this.info.age = 25; // 状态变量age改变
1180        })
1181    }
1182  }
1183}
1184```
1185