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```