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