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