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