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