• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# addMonitor/clearMonitor接口:动态添加/取消监听
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @liwenzhen3-->
5<!--Designer: @s10021109-->
6<!--Tester: @TerryTsao-->
7<!--Adviser: @zhang_yixin13-->
8
9为了动态添加或删除状态管理V2的状态变量的监听函数,开发者可以使用[addMonitor](../../reference/apis-arkui/js-apis-StateManagement.md#addmonitor20)或[clearMonitor](../../reference/apis-arkui/js-apis-StateManagement.md#clearmonitor20)。
10
11在阅读本文档前,建议提前阅读:[\@ObservedV2/\@Trace](./arkts-new-observedV2-and-trace.md)、[\@Monitor](./arkts-new-monitor.md)。
12
13>**说明:**
14>
15>从API version 20开始,开发者可以使用UIUtils中的addMonitor/clearMonitor接口动态给状态管理V2的状态变量添加或删除监听函数。
16
17
18## 概述
19装饰器[\@Monitor](./arkts-new-monitor.md)如果声明在[\@ObservedV2](./arkts-new-observedV2-and-trace.md)和[\@ComponentV2](./arkts-new-componentV2.md)中,会使得开发者构造出的所有的\@ObservedV2和\@ComponentV2的实例,都默认有同样的\@Monitor的监听回调,且无法取消或删除对应的监听回调。
20
21如果开发者希望动态给\@ObservedV2和\@ComponentV2实例添加或者删除监听函数,则可以使用[addMonitor](../../reference/apis-arkui/js-apis-StateManagement.md#addmonitor20)和[clearMonitor](../../reference/apis-arkui/js-apis-StateManagement.md#clearmonitor20)接口。
22
23- 使用addMonitor/clearMonitor接口需要导入UIUtils工具。
24
25  ```ts
26  import { UIUtils } from '@kit.ArkUI';
27  ```
28- 仅支持监听状态管理V2的状态变量的变化。
29
30- clearMonitor仅可以删除addMonitor添加的监听函数,无法删除\@Monitor的监听函数。
31
32## 使用规则
33- addMonitor/clearMonitor可以传入数组一次性给多个状态变量添加或删除回调函数。
34```ts
35import { UIUtils } from '@kit.ArkUI';
36
37@ObservedV2
38class User {
39  @Trace age: number = 0;
40  @Trace name: string = 'Jack';
41
42  onChange1(mon: IMonitor) {
43    mon.dirty.forEach((path: string) => {
44      console.info(`onChange1: User property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
45    });
46  }
47
48  constructor() {
49    UIUtils.addMonitor(this, ['age', 'name'], this.onChange1);
50  }
51}
52
53@Entry
54@ComponentV2
55struct Page {
56  user: User = new User();
57
58  build() {
59    Column() {
60      Text(`User name ${this.user.name}`)
61        .fontSize(20)
62        .onClick(() => {
63          // 改变name,回调onChange1监听函数
64          this.user.name += '!';
65        })
66      Text(`User age ${this.user.age}`)
67        .fontSize(20)
68        .onClick(() => {
69          // age自增,回调onChange1监听函数
70          this.user.age++;
71        })
72      Button('clear name and age monitor fun')
73        .onClick(() => {
74          // 删除age和name的onChange1监听函数
75          // 再次点击Text组件改变name和age,无监听函数回调
76          UIUtils.clearMonitor(this.user, ['age', 'name'], this.user.onChange1);
77        })
78    }
79  }
80}
81```
82- addMonitor可以给path对应的状态变量添加多个监听函数,但是需要注意,如果开发者添加同名的监听函数,则会添加失败,打印错误日志。
83```ts
84import { UIUtils } from '@kit.ArkUI';
85
86@ObservedV2
87class User {
88  @Trace age: number = 0;
89
90  onChange1(mon: IMonitor) {
91    mon.dirty.forEach((path: string) => {
92      console.info(`onChange1: User property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
93    });
94  }
95
96  onChange2(mon: IMonitor) {
97    mon.dirty.forEach((path: string) => {
98      console.info(`onChange2: User property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
99    });
100  }
101
102  constructor() {
103    // 正确用法,给age注册监听函数onChange1
104    UIUtils.addMonitor(this, 'age', this.onChange1);
105    // 正确用法,给age注册监听函数onChange2
106    UIUtils.addMonitor(this, 'age', this.onChange2);
107  }
108}
109
110@Entry
111@ComponentV2
112struct Page {
113  user: User = new User();
114
115  onChange1(mon: IMonitor) {
116    mon.dirty.forEach((path: string) => {
117      console.info(`onChange1 in View: User property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
118    });
119  }
120
121  aboutToAppear(): void {
122    // 错误用法,已经给age注册过方法名为onChange1的函数,无法重复注册相同函数名的监听函数
123    // 打印错误日志提示添加失败:FIX THIS APPLICATION ERROR: AddMonitor onChange1 failed when adding path age because duplicate key
124    UIUtils.addMonitor(this.user, 'age', this.onChange1);
125  }
126
127  build() {
128    Column() {
129      Text(`User age ${this.user.age}`)
130        .fontSize(20)
131        .onClick(() => {
132          // age自增,回调User中的onChange1和onChange2方法
133          this.user.age++;
134        })
135    }
136  }
137}
138```
139- addMonitor设置[isSynchronous](../../reference/apis-arkui/js-apis-StateManagement.md#monitoroptions20)仅第一次有效,即其不能被更改,如果开发者更改`isSynchronous`,则会打印错误日志。
140```ts
141import { UIUtils } from '@kit.ArkUI';
142
143@ObservedV2
144class User {
145  @Trace age: number = 0;
146
147  onChange1(mon: IMonitor) {
148    mon.dirty.forEach((path: string) => {
149      console.info(`onChange1: User property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
150    });
151  }
152
153  constructor() {
154    // 正确用法,给a注册监听函数onChange1,没有设置options默认为异步监听回调
155    UIUtils.addMonitor(this, 'age', this.onChange1);
156    // 错误用法,不能改变this.onChange1的监听回调的方式
157    // 打印错误日志提示: FIX THIS APPLICATION ERROR: addMonitor failed, current function onChange1 has already register as async, cannot change to sync anymore
158    UIUtils.addMonitor(this, 'age', this.onChange1, { isSynchronous: true });
159  }
160}
161
162@Entry
163@ComponentV2
164struct Page {
165  user: User = new User();
166
167  build() {
168    Column() {
169      Text(`User age ${this.user.age}`)
170        .fontSize(20)
171        .onClick(() => {
172          // age自增,回调onChange1,回调方式为异步回调
173          // 监听回调的日志:onChange1: User property age change from 0 to 2
174          this.user.age++;
175          this.user.age++;
176        })
177    }
178  }
179}
180```
181- clearMonitor可以删除path对应的状态变量的监听函数,开发者可以通过传入监听回调函数来指定删除具体的监听函数,也可以不指定具体的监听函数,删除当前path对应状态变量的所有监听回调函数。
182需要注意:当调用clearMonitor时,如果发现当前回调函数没有在path对应的状态变量上注册过,或者当前状态变量没有任何监听函数,都会打印告警日志提示开发者删除失败。
183监听函数被删除后,状态变量的改变不会再回调对应的监听函数。
184```ts
185import { UIUtils } from '@kit.ArkUI';
186
187@ObservedV2
188class User {
189  @Trace age: number = 0;
190  @Trace name: string = 'Jack';
191
192  onChange1(mon: IMonitor) {
193    mon.dirty.forEach((path: string) => {
194      console.info(`onChange1: User property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
195    });
196  }
197
198  onChange2(mon: IMonitor) {
199    mon.dirty.forEach((path: string) => {
200      console.info(`onChange2: User property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
201    });
202  }
203
204  onChange3(mon: IMonitor) {
205    mon.dirty.forEach((path: string) => {
206      console.info(`onChange3: User property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
207    });
208  }
209
210  constructor() {
211    UIUtils.addMonitor(this, 'age', this.onChange1);
212    UIUtils.addMonitor(this, 'age', this.onChange2);
213    UIUtils.addMonitor(this, 'age', this.onChange3);
214  }
215}
216
217@Entry
218@ComponentV2
219struct Page {
220  user: User = new User();
221
222  build() {
223    Column() {
224      Text(`User age ${this.user.age}`)
225        .fontSize(20)
226        .onClick(() => {
227          // step1:点击age,回调onChange1,onChange2,onChange3
228          this.user.age++;
229        })
230      Button('clear age onChange1').onClick(() => {
231        // step2:第一次点击该Button。删除onChange1,删除成功。此时点击User age,仅会回调onChange2,onChange3
232        // step3:再次点击该Button。再次删除onChange1,onChange1已经被删除,此次删除失败
233        // 打印错误日志:FIX THIS APPLICATION ERROR: cannot clear path age for onChange1 because it was never registered with addMonitor
234        UIUtils.clearMonitor(this.user, 'age', this.user.onChange1);
235      })
236      Button('clear age monitors').onClick(() => {
237        // step4:删除age所有添加的监听函数。再次点击User age,无监听函数回调
238        UIUtils.clearMonitor(this.user, 'age');
239      })
240      Button('clear name monitors').onClick(() => {
241        // step5:删除name添加的监听方法。因为name无任何监听回调,删除失败
242        // 打印错误日志:FIX THIS APPLICATION ERROR: cannot clear path name for current target User because no Monitor function for this path was registered
243        UIUtils.clearMonitor(this.user, 'name');
244      })
245    }
246  }
247}
248```
249
250## 限制条件
251- addMonitor/clearMonitor仅支持对\@ComponentV2和\@ObservedV2装饰(至少有一个\@Trace装饰的变量)的实例添加/取消回调,否则会有运行时报错,错误码为130000。
252下面为addMonitor的例子,clearMonitor同理。
253  ```ts
254  import { UIUtils } from '@kit.ArkUI';
255
256  @ObservedV2
257  class A {
258    @Trace a: number = 0;
259
260    onChange(mon: IMonitor) {
261      mon.dirty.forEach((path: string) => {
262        console.info(`A property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
263      });
264    }
265
266    constructor() {
267      // 正确用法
268      UIUtils.addMonitor(this, 'a', this.onChange);
269    }
270  }
271
272  @Observed
273  class B {
274    @Track b: number = 0;
275
276    onChange(mon: IMonitor) {
277      mon.dirty.forEach((path: string) => {
278        console.info(`B property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
279      });
280    }
281
282    constructor() {
283      // 目标对象非法入参,当前this为@Observed装饰的对象
284      // Error code: 130000
285      UIUtils.addMonitor(this, 'b', this.onChange);
286    }
287  }
288
289  class C {
290    @Track c: number = 0;
291
292    onChange(mon: IMonitor) {
293      mon.dirty.forEach((path: string) => {
294        console.info(`C property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
295      });
296    }
297
298    constructor() {
299      // 错误用法:目标对象非法入参,当前this为普通class
300      // Error code: 130000
301      UIUtils.addMonitor(this, 'c', this.onChange);
302      // 错误用法:目标对象非法入参undefined
303      // Error code: 130000
304      UIUtils.addMonitor(undefined, 'c', this.onChange);
305    }
306  }
307
308  let a: A = new A();
309  let b: B = new B();
310  let c: C = new C();
311  ```
312- addMonitor/clearMonitor观察路径必须为string或者为数组,如果开发者传入不支持的类型,则会有运行时报错,错误码为130001。
313下面为addMonitor的例子,clearMonitor同理。
314  ```ts
315  import { UIUtils } from '@kit.ArkUI';
316
317  @ObservedV2
318  class A {
319    @Trace a: number = 0;
320    @Trace b: number = 0;
321    invalidPath: number | string = 0;
322
323    onChange(mon: IMonitor) {
324      mon.dirty.forEach((path: string) => {
325        console.info(`A property ${path} change from ${mon.value(path)?.before} to   ${mon.value(path)?.now}`);
326      });
327    }
328
329    constructor() {
330      // 正确用法
331      UIUtils.addMonitor(this, 'a', this.onChange);
332      // 正确用法
333      UIUtils.addMonitor(this, ['a', 'b'], this.onChange);
334      // 错误用法,path必须为string或数组,会发生运行时校验,错误码为130001
335      UIUtils.addMonitor(this, this.invalidPath as string, this.onChange);
336      // 错误用法,path必须为string或数组,会发生运行时校验,错误码为130001
337      UIUtils.addMonitor(this, undefined, this.onChange);
338    }
339  }
340
341  let a: A = new A();
342  ```
343- addMonitor的回调函数必须存在,类型必须为方法类型,且不能为匿名函数,如果开发者传入不支持的类型,则会有运行时报错,错误码为130002。
344clearMonitor开发者可以不设置回调函数,如果设置了,其类型必须为function类型,且不能为匿名函数。
345  ```ts
346  import { UIUtils } from '@kit.ArkUI';
347
348  @ObservedV2
349  class A {
350    @Trace a: number = 0;
351    @Trace b: number = 0;
352    invalidFunc: Function | number = 0;
353
354    onChange1(mon: IMonitor) {
355      mon.dirty.forEach((path: string) => {
356        console.info(`A property ${path} change from ${mon.value(path)?.before} to   ${mon.value(path)?.now}`);
357      });
358    }
359
360    onChange2(mon: IMonitor) {
361      mon.dirty.forEach((path: string) => {
362        console.info(`A property ${path} change from ${mon.value(path)?.before} to   ${mon.value(path)?.now}`);
363      });
364    }
365
366    constructor() {
367      // 正确用法,给变量a添加函数onChange1
368      UIUtils.addMonitor(this, 'a', this.onChange1);
369      // 正确用法,给变量a添加函数onChange2
370      UIUtils.addMonitor(this, 'a', this.onChange2);
371      // 正确用法,给变量b添加函数onChange1
372      UIUtils.addMonitor(this, 'b', this.onChange1);
373      // 错误用法。传入的回调函数为非function类型,错误码130002
374      UIUtils.addMonitor(this, 'a', undefined);
375      // 错误用法,传入的回调函数为匿名函数,错误码130002
376      UIUtils.addMonitor(this, 'a', (mon: IMonitor) => {});
377      // 错误用法,绕过编译器检查,传入的回调函数为非Function类型,错误码130002
378      UIUtils.addMonitor(this, 'a', this.invalidFunc as (mon: IMonitor) => void);
379    }
380  }
381
382  let a: A = new A();
383  // 正确用法,删除a注册的监听函数onChange1
384  UIUtils.clearMonitor(a, 'a', a.onChange1);
385  // 正确用法,删除a所有的监听函数
386  UIUtils.clearMonitor(a, 'a');
387  // 正确用法。等于不传参数,删除b所有的监听函数
388  UIUtils.clearMonitor(a, 'a', undefined);
389  // 错误用法,传入的回调函数为匿名函数,错误码130002
390  UIUtils.clearMonitor(a, 'a', (mon: IMonitor) => {});
391  ```
392
393## addMonitor监听变化的规则
394addMonitor和装饰器[\@Monitor](./arkts-new-monitor.md)监听变化的主要规则大体保持一致,对比如下表:
395
396|  场景 | addMonitor| @Monitor  |
397|------|----|------|
398| [监听\@ObservedV2类中@Trace修饰属性的变化](#监听observedv2类中trace修饰属性和componentv2组件中状态变量的变化)   | 支持 | 支持 |
399| [监听\@ComponentV2组件中状态变量的变化](#监听observedv2类中trace修饰属性和componentv2组件中状态变量的变化) | 支持 | 支持 |
400| [监听数组类型状态变量的下标和length的变化](#监听数组类型状态变量的下标和length的变化)  | 支持 | 支持 |
401| 监听Map、Set、Date类型状态变量变化  | 不支持 | 不支持 |
402| [独立监听path变化](#独立监听path) | 支持 | 不支持 |
403| [监听变量从可访问到不访问和从不可访问到可访问](#监听变量从可访问到不访问和从不可访问到可访问)  | 支持 | 不支持 |
404| [配置同步监听函数](#配置同步监听函数)  | 支持 | 不支持 |
405| [监听构造函数中同步修改的状态变量的变化](#监听构造函数中同步修改的状态变量的变化)  | 支持 | 不支持 |
406| [动态取消@ObservedV2/@ComponentV2实例的监听](#动态取消observedv2componentv2实例的监听)  | 支持 | 不支持 |
407
408## 使用场景
409### 监听\@ObservedV2类中@Trace修饰属性和\@ComponentV2组件中状态变量的变化
410
411在下面的例子中:
412- 在User的构造函数中添加对`age`和`name`的监听函数`onChange`。
413- 在自定义组件`Page`的`aboutToAppear`的生命周期中,添加对`user`的监听函数`onChangeInView`。
414- 点击```Text(`User name ${this.user.name}`)```,改变`name`的值,触发`onChange`方法。
415- 点击```Text(`User age ${this.user.age}`)```,改变`age`的值,触发`onChange`方法。
416- 点击```Text(`reset User`)```,对`user`整体赋值,触发`onChangeInView`方法。
417```ts
418import { UIUtils } from '@kit.ArkUI';
419
420@ObservedV2
421class User {
422  @Trace age: number = 0;
423  @Trace name: string = 'Jack';
424
425  onChange(mon: IMonitor) {
426    mon.dirty.forEach((path: string) => {
427      console.info(`onChange: User property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
428    });
429  }
430
431  constructor() {
432    UIUtils.addMonitor(this, ['age', 'name'], this.onChange);
433  }
434}
435
436@Entry
437@ComponentV2
438struct Page {
439  @Local user: User = new User();
440
441  onChangeInView(mon: IMonitor) {
442    mon.dirty.forEach((path: string) => {
443      console.info(`onChange in View: View property ${path} change from ${JSON.stringify(mon.value(path)?.before)} to ${JSON.stringify(mon.value(path)?.now)}`);
444    });
445  }
446
447  aboutToAppear(): void {
448    UIUtils.addMonitor(this, 'user', this.onChangeInView);
449  }
450
451  build() {
452    Column() {
453      Text(`User name ${this.user.name}`)
454        .fontSize(20)
455        .onClick(() => {
456          // 改变name,回调onChange监听函数
457          this.user.name += '!';
458        })
459      Text(`User age ${this.user.age}`)
460        .fontSize(20)
461        .onClick(() => {
462          // age自增,回调onChange监听函数
463          this.user.age++;
464        })
465      Text(`reset User`)
466        .fontSize(20)
467        .onClick(() => {
468          // user整体赋值,回调onChangeInView监听函数
469          this.user = new User();
470        })
471    }
472  }
473}
474```
475
476### 监听数组类型状态变量的下标和length的变化
477
478下面的例子展示了对Array数组下标和length的监听。
479```ts
480import { UIUtils } from '@kit.ArkUI';
481
482@Entry
483@ComponentV2
484struct Page {
485  @Local arr: string[] = ['a', 'b', 'c']
486
487  onChange(mon: IMonitor) {
488    mon.dirty.forEach((path: string) => {
489      console.info(`onChange: View property ${path} change from ${JSON.stringify(mon.value(path)?.before)} to ${JSON.stringify(mon.value(path)?.now)}`);
490    });
491  }
492
493  aboutToAppear(): void {
494    // 添加对数组index为0,1,2和数组length的监听回调onChange
495    UIUtils.addMonitor(this, ['arr.0', 'arr.1', 'arr.2', 'arr.length'], this.onChange);
496  }
497
498  build() {
499    Column() {
500      Text(`len ${this.arr.length}`).fontSize(20)
501      Text(`${this.arr[0]}`).fontSize(20).onClick(() => {
502        // 改变数组index为0的数组项
503        // onChange回调:onChange: View property arr.0 change from "a" to "az"
504        this.arr[0] += 'z';
505      })
506      Text(`${this.arr[1]}`).fontSize(20).onClick(() => {
507        // 改变数组index为1的数组项
508        // onChange回调:onChange: View property arr.1 change from "b" to "bz"
509        this.arr[1] += 'z';
510      })
511      Text(`${this.arr[2]}`).fontSize(20).onClick(() => {
512        // 改变数组index为2的数组项
513        // onChange回调:onChange: View property arr.2 change from "c" to "cz"
514        this.arr[2] += 'z';
515      })
516      Text(`push`).fontSize(20).onClick(() => {
517        // 在数组末尾push新数组项'd',其index为4,index为4没有被监听
518        // 数组长度改变,length被监听
519        // onChange回调:onChange: View property arr.length change from 3 to 4
520        this.arr.push('d');
521      })
522      Text(`shift`).fontSize(20).onClick(() => {
523        // 删除数组第一个元素
524        // 0: az -> bz
525        // 1: bz -> cz
526        // 2: cz -> d
527        // length: 4 -> 3
528        // onChange回调:
529        // onChange: View property arr.0 change from "az" to "bz"
530        // onChange: View property arr.1 change from "bz" to "cz"
531        // onChange: View property arr.2 change from "cz" to "d"
532        // onChange: View property arr.length change from 4 to 3
533        this.arr.shift();
534      })
535    }
536  }
537}
538```
539
540### 独立监听Path
541\@Monitor没有对path独立监听,所以需要依赖开发者正确传入\@Monitor入参,[传入非状态变量时会造成被连带监听的情况](./arkts-new-monitor.md#正确设置monitor入参)。
542对于addMonitor,对不同path采取了独立监听的机制,如下面的例子,点击`Button('change age&name')`,会输出以下日志:
543```
544property path:age change from 24 to 25
545```
546
547```ts
548import { UIUtils } from '@kit.ArkUI';
549
550@ObservedV2
551class Info {
552  name: string = 'John';
553  @Trace age: number = 24;
554
555  onPropertyChange(monitor: IMonitor) {
556    monitor.dirty.forEach((path: string) => {
557      console.info(`property path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
558    });
559  }
560
561  constructor() {
562    UIUtils.addMonitor(this, ['age', 'name'], this.onPropertyChange);
563  }
564}
565
566@Entry
567@ComponentV2
568struct Index {
569  info: Info = new Info();
570  build() {
571    Column() {
572      Button('change age&name')
573        .onClick(() => {
574          this.info.age = 25; // 同时改变状态变量age和非状态变量name
575          this.info.name = 'Johny';
576        })
577    }
578  }
579}
580```
581
582### 监听变量从可访问到不访问和从不可访问到可访问
583[\@Monitor](./arkts-new-monitor.md#无法监听变量从可访问变为不可访问和从不可访问变为可访问
584)不会记录状态变量不可访问时的状态,所以其无法监听变量从可访问到不访问和从不可访问到可访问。
585addMonitor会记录变量不可访问的状态,所以可以监听变量从可访问到不访问和从不可访问到可访问。例子如下。
586
587```ts
588import { UIUtils } from '@kit.ArkUI';
589
590@ObservedV2
591class User {
592  @Trace age: number = 10;
593}
594
595@Entry
596@ComponentV2
597struct Page {
598  @Local user: User | undefined | null = new User();
599
600  onChange(mon: IMonitor) {
601    mon.dirty.forEach((path: string) => {
602      console.info(`onChange: User property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
603    });
604  }
605
606  aboutToAppear() {
607    UIUtils.addMonitor(this, ['user.age'], this.onChange);
608  }
609
610  build() {
611    Column() {
612      Text(`User age ${this.user?.age}`).fontSize(20)
613      Button('set user to undefined').onClick(() => {
614          // age可访问->不可访问
615          // 触发onChange监听回调:onChange: User property user.age change from 10 to undefined
616          this.user = undefined;
617        })
618      Button('set user to User').onClick(() => {
619        // age不可访问->可访问
620        // 触发onChange监听回调:onChange: User property user.age change from undefined to 10
621        this.user = new User();
622      })
623      Button('set user to null').onClick(() => {
624        // age可访问->不可访问
625        // 触发onChange监听回调:onChange: User property user.age change from 10 to undefined
626        this.user = null;
627      })
628    }
629  }
630}
631```
632### 配置同步监听函数
633和\@Monitor仅支持异步监听不同,addMonitor可支持配置成同步监听函数,在下面的例子中,点击```Text(`User age ${this.user.age}`)```,触发两次`age`的自增,回调两次`onChange`函数,日志打印如下:
634```
635onChange: User property user.age change from 10 to 11
636onChange: User property user.age change from 11 to 12
637```
638```ts
639import { UIUtils } from '@kit.ArkUI';
640
641@ObservedV2
642class User {
643  @Trace age: number = 10;
644}
645
646@Entry
647@ComponentV2
648struct Page {
649  @Local user: User = new User();
650
651  onChange(mon: IMonitor) {
652    mon.dirty.forEach((path: string) => {
653      console.info(`onChange: User property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
654    });
655  }
656
657  aboutToAppear(): void {
658    UIUtils.addMonitor(this, 'user.age', this.onChange, { isSynchronous: true })
659  }
660
661  build() {
662    Column() {
663      Text(`User age ${this.user.age}`).fontSize(20).onClick(() => {
664        this.user.age++;
665        this.user.age++;
666      })
667    }
668  }
669}
670```
671如果将上面的例子改成\@Monitor,仅会打印一次回调,日志如下:
672```
673onChange: User property user.age change from 10 to 12
674```
675
676```ts
677@ObservedV2
678class User {
679  @Trace age: number = 10;
680}
681
682@Entry
683@ComponentV2
684struct Page {
685  @Local user: User = new User();
686
687  @Monitor('user.age')
688  onChange(mon: IMonitor) {
689    mon.dirty.forEach((path: string) => {
690      console.info(`onChange: User property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
691    });
692  }
693
694  build() {
695    Column() {
696      Text(`User age ${this.user.age}`).fontSize(20).onClick(() => {
697        this.user.age++;
698        this.user.age++;
699      })
700    }
701  }
702}
703```
704
705### 监听构造函数中同步修改的状态变量的变化
706和[\@Monitor异步构造](./arkts-new-monitor.md#类中monitor对变量监听的生效及失效时间)不同,addMonitor是同步构造的,所以在开发者调用完`UIUtils.addMonitor(this, 'message', this.onMessageChange);`后就完成了对`message`添加监听函数`this.onMessageChange`。在下面的例子中:
707- 拉起页面,构造`Info`的实例,回调`onMessageChange`监听函数。
708- 点击```Button('change message')```,回调`onMessageChange`监听函数。
709日志输出如下:
710```
711message change from not initialized to initialized
712message change from initialized to Index aboutToAppear
713message change from Index aboutToAppear to Index click to change message
714```
715
716```ts
717import { UIUtils } from '@kit.ArkUI';
718
719@ObservedV2
720class Info {
721  @Trace message: string = 'not initialized';
722
723  constructor() {
724    UIUtils.addMonitor(this, 'message', this.onMessageChange);
725    this.message = 'initialized';
726  }
727  onMessageChange(monitor: IMonitor) {
728    console.info(`message change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
729  }
730}
731
732@Entry
733@ComponentV2
734struct Page {
735  info: Info = new Info();
736
737  aboutToAppear(): void {
738    this.info.message = 'Index aboutToAppear';
739  }
740
741  build() {
742    Column() {
743      Button('change message')
744        .onClick(() => {
745          this.info.message = 'Index click to change message';
746        })
747    }
748  }
749}
750```
751
752### 动态取消\@ObservedV2/\@ComponentV2实例的监听
753
754和@Monitor不同,addMonitor/clearMonitor可以对不同的\@ObservedV2/\@ComponentV2实例动态添加监听函数。例子如下。
755
756```ts
757import { UIUtils } from '@kit.ArkUI';
758
759@ObservedV2
760class User {
761  @Trace age: number = 10;
762
763  onChange(mon: IMonitor) {
764    mon.dirty.forEach((path: string) => {
765      console.info(`onChange: User property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
766    });
767  }
768
769  constructor(needMonitor: boolean) {
770    if (needMonitor) {
771      UIUtils.addMonitor(this, 'age', this.onChange);
772    }
773  }
774}
775
776@Entry
777@ComponentV2
778struct Page {
779  @Local user1: User = new User(true);
780  @Local user2: User = new User(false);
781  @Local count: number = 10;
782
783  build() {
784    Column() {
785      Text(`user1 age ${this.user1.age}`).fontSize(20).onClick(() => {
786        // 有Monitor回调
787        this.user1.age++;
788      })
789      Text(`user2 age ${this.user2.age}`).fontSize(20).onClick(() => {
790        // 无Monitor回调
791        this.user2.age++;
792      })
793      Button(`remove user1 monitor`).onClick(() => {
794        UIUtils.clearMonitor(this.user1, 'age', this.user1.onChange);
795      })
796
797      Button(`change count`).onClick(() => {
798        this.count++;
799      })
800
801      Child({ needMonitor: true, count: this.count })
802      Child({ needMonitor: false, count: this.count })
803    }
804  }
805}
806
807@ComponentV2
808struct Child {
809  @Param needMonitor: boolean = false;
810  @Param count: number = 0;
811
812  onChange(mon: IMonitor) {
813    mon.dirty.forEach((path: string) => {
814      console.info(`Child needMonitor ${this.needMonitor} onChange: property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
815    });
816  }
817
818  aboutToAppear(): void {
819    if (this.needMonitor) {
820      UIUtils.addMonitor(this, 'count', this.onChange);
821    }
822  }
823
824  build() {
825    Column() {
826      Text(`${this.count}`).fontSize(20)
827    }
828  }
829}
830```