目录

HarmonyOS-Next-状态管理Monitor-装饰器实践

HarmonyOS Next 状态管理:Monitor 装饰器实践

目录

一. @Monitor 修饰器概述

@Monitor 是鸿蒙开发中用于增强状态管理框架对状态变量变化监听能力的一个装饰器。它能够监听状态变量的变化,并提供比 @Watch 更强大的功能,包括深度监听嵌套对象、数组等复杂数据结构的变化,并能够获取变化前后的值。

核心特性:
  1. 使用场景
  • @Monitor 装饰器需在 @ComponentV2 装饰的自定义组件中使用。
  • 被监听的变量需被状态装饰器(如 @Local@Param@Provider@Consumer@Computed)修饰,否则无法监听其变化。
  1. 独立使用
  • @Monitor 装饰器支持在类中独立使用,但该类需被 @ObserverV2 修饰。
  • 在组件中,@Monitor 可与 @ObserverV2@Trace 配合使用,通过回调方法监听类属性的变化。
  1. 变化检测机制
  • 判断修饰对象是否变化时,使用严格相等(===)进行比较。若结果为 false(即不相等),则触发 @Monitor 的回调。
  • 若在一次事件中多次改变同一属性,@Monitor 会使用初始值和最终值进行比较。
  1. 多属性监听
  • 单个 @Monitor 装饰器可同时监听多个属性的变化。若这些属性在一次事件中共同变化,仅触发一次回调。
  1. 深度监听
  • @Monitor 支持深度监听嵌套类、多维数组、对象数组中指定项的变化。
  • 对于嵌套类或对象数组中成员属性的监听,要求该类被 @ObserverV2 修饰,且该属性被 @Trace 修饰。
  1. 继承场景
  • 在继承类中,父子类可分别对同一属性定义 @Monitor 监听。当属性变化时,父子类中的 @Monitor 回调均会被调用。
  1. 回调函数
  • @Watch 装饰器类似,开发者需自定义回调函数。区别在于,@Watch 将函数名作为参数,而 @Monitor 直接装饰回调函数。

二. @Monitor与@Watch对比

特性@Watch@Monitor
参数回调方法名监听状态变量名、属性名。
监听目标数只能监听单个状态变量能同时监听多个状态变量
监听能力只能监听一层属性支持深度监听嵌套属性
能否获取变化前的值不能获取变化前的值能获取变化前后的值
监听条件监听对象需为状态变量监听对象为状态变量或为@Trace装饰的类成员属性
使用限制仅能在 @Component 装饰的自定义组件中使用可在 @ComponentV2 装饰的自定义组件或 @ObservedV2
装饰的类中使用

三. @Monitor的简单使用

3.1 代码演示

import { promptAction } from ‘@kit.ArkUI’ @Entry @ComponentV2 struct Index { @Local msg: string = “孙膑” @Monitor(‘msg’) onMsgChange(monitor: IMonitor) { monitor.dirty.forEach((path) => { const toastMsg = ${path} before:${monitor.value(path)?.before}, now:${monitor.value(path)?.now} console.log(${path} before:${monitor.value(path)?.before}, now:${monitor.value(path)?.now}) promptAction.showToast({ message: toastMsg, alignment: Alignment.Center }) }) } build() { Column({space: 20}) { Text(msg:${this.msg}) Button(‘change msg’) .onClick(() => { this.msg = ${Date.now().toString()} }) } .width(‘100%’) } } ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img- home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fp0-xtjj- private.juejin.cn%2Ftos- cn-i-73owjymdk6%2Ff37187e4236b4317824c681bab16cf59~tplv-73owjymdk6-jj- mark-v1%3A0%3A0%3A0%3A0%3A5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5pyI5pyq5aSu%3Aq75.awebp%3Fpolicy%3DeyJ2bSI6MywidWlkIjoiMTY3OTcwOTQ5NDg0OTYyMyJ9%26rk3s%3Df64ab15b%26x-orig- authkey%3Df32326d3454f2ac7e96d3d06cdbb035152127018%26x-orig- expires%3D1742666482%26x-orig- sign%3DuCpt36%252FJxBSxdroELZVZxPgzlcs%253D&pos_id=img-ZON8rMth-1742061703385) 关键点:

  • @Monitor 修饰的是字符串类型,其值是对象的变量名
  • @Monitor 装饰的变量不支持动态修改,即不支持在运行时修改。
3.2 拆解代码
3.2.1 IMonitor 接口

IMonitor 是一个接口类型,用于在 @Monitor 的回调函数中获取状态变量变化的信息。它的定义如下: interface IMonitor { dirty: Array; // 发生变化的属性名数组 value(path?: string): IMonitorValue; // 获取指定属性的变化信息 }

属性
  • dirty :一个字符串数组,存储了发生变化的属性名。例如,如果监听了 messagename,当 message 发生变化时,dirty 数组会包含 "message"
方法
  • value(path?: string) :获取指定属性(path)的变化信息。如果不传递 path,则返回第一个发生变化的属性的信息。返回值是一个 IMonitorValue 对象,包含变化前后的值。
3.2.2 IMonitorValue 接口

IMonitorValue 是一个泛型接口,用于存储属性变化的信息。它的定义如下: interface IMonitorValue { before: T; // 变化前的值 now: T; // 变化后的值 path: string; // 监听的属性名 }

属性
  • before :属性变化前的值。
  • now :属性变化后的值。
  • path :监听的属性名。
3.2.3 dirty 的作用

dirty 是一个数组,存储了所有发生变化的属性名。它的作用是:

  • @Monitor 监听多个属性时,dirty 可以帮助开发者快速知道哪些属性发生了变化。
  • 通过遍历 dirty 数组,可以逐个处理发生变化的属性。

四、实践探索

4.1 @Monitor 监听基础类型

@Monitor 可以直接修饰基础类型(如 stringnumberbooleanenumnullundefined 等),并在变量变化时更新关联的 UI 组件。 import { promptAction } from ‘@kit.ArkUI’ enum Gender { Female, Male } @Entry @ComponentV2 struct Index { msg: string = “非状态变量” @Local name: string = “孙膑” @Local age: number = 28 @Local isMaimed: boolean = true @Local occupation: undefined | string = undefined @Local consort: null | string = null @Local gender: Gender = Gender.Male @Monitor(‘msg’) onMsgChange(monitor: IMonitor) { monitor.dirty.forEach((path) => { promptAction.showToast({ message: msg changed, before:${monitor.value(path)?.before}, now:${monitor.value(path)?.now}, alignment: Alignment.Center }) }) } @Monitor(’name’) onNameChange(monitor: IMonitor) { monitor.dirty.forEach((path) => { promptAction.showToast({ message: name changed, before:${monitor.value(path)?.before}, now:${monitor.value(path)?.now}, alignment: Alignment.Center }) }) } @Monitor(‘age’, ‘isMaimed’, ‘occupation’, ‘consort’, ‘gender’) onVariablesChange(monitor: IMonitor) { monitor.dirty.forEach((path) => { const toastMsg = ${path} changed, before:${monitor.value(path)?.before}, now:${monitor.value(path)?.now} console.log(toastMsg); promptAction.showToast({ message: toastMsg, alignment: Alignment.Center }) }) } build() { Column({ space: 10 }) { Text(msg:${this.msg}) Text(name:${this.name}) Text(age:${this.age}) Text(isMaimed:${this.isMaimed}) Text(occupation:${this.occupation}) Text(consort:${this.consort}) Text(gender:${this.gender}) Button(‘更新非状态变量:msg’) .onClick(() => { this.msg = “非状态变量” + ${Math.round(Math.random() * 1000)} }) Button(‘更新变量:name’) .onClick(() => { this.name = “小乔”+ ${Math.round(Math.random() * 1000)} }) Button(‘更新其他所有变量’) .onClick(() => { this.age++ this.isMaimed = !this.isMaimed this.occupation = “未知” + ${Math.round(Math.random() * 1000)} this.consort = “周瑜” + ${Math.round(Math.random() * 1000)} this.gender = (this.gender == Gender.Female) ? Gender.Male : Gender.Female }) } .height(‘100%’) .width(‘100%’) } } ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img- home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fp0-xtjj- private.juejin.cn%2Ftos- cn-i-73owjymdk6%2F8e507cea847045cf8ee34d1fb60b5325~tplv-73owjymdk6-jj- mark-v1%3A0%3A0%3A0%3A0%3A5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5pyI5pyq5aSu%3Aq75.awebp%3Fpolicy%3DeyJ2bSI6MywidWlkIjoiMTY3OTcwOTQ5NDg0OTYyMyJ9%26rk3s%3Df64ab15b%26x-orig- authkey%3Df32326d3454f2ac7e96d3d06cdbb035152127018%26x-orig- expires%3D1742666482%26x-orig- sign%3Dl1msoIeDy5%252Fcoi%252Bf5kUmUnE88R4%253D&pos_id=img-0UnPqYcm-1742061703387) 关键点:

  • 非状态变量的更新不会触发 @Monitor 回调。
  • @Monitor 支持修饰 stringnumberbooleanenumnullundefined 等基础类型。 // 第一次点击 03-15 07:23:16.982 32643-32643 A03d00/JSAPP com.examp…lication I age changed, before:28, now:29 03-15 07:23:16.983 32643-32643 A03d00/JSAPP com.examp…lication I isMaimed changed, before:true, now:false 03-15 07:23:16.983 32643-32643 A03d00/JSAPP com.examp…lication I occupation changed, before:undefined, now:未知807 03-15 07:23:16.983 32643-32643 A03d00/JSAPP com.examp…lication I consort changed, before:null, now:周瑜424 03-15 07:23:16.983 32643-32643 A03d00/JSAPP com.examp…lication I gender changed, before:1, now:0 // 第二次点击 03-15 07:23:19.553 32643-32643 A03d00/JSAPP com.examp…lication I age changed, before:29, now:30 03-15 07:23:19.553 32643-32643 A03d00/JSAPP com.examp…lication I isMaimed changed, before:false, now:true 03-15 07:23:19.553 32643-32643 A03d00/JSAPP com.examp…lication I occupation changed, before:未知807, now:未知232 03-15 07:23:19.553 32643-32643 A03d00/JSAPP com.examp…lication I consort changed, before:周瑜424, now:周瑜178 03-15 07:23:19.553 32643-32643 A03d00/JSAPP com.examp…lication I gender changed, before:0, now:1
  • 不支持的类型@Monitor 不支持修饰 anyunknown 类型。
  • 单个@Monitor 可以监听单个变量(例如: @Monitor(‘name’))的变化,也支持监听多个变化的变化(例如:@Monitor(‘age’, ‘isMaimed’, ‘occupation’, ‘consort’, ‘gender’))
  • 单个 @Monitor 装饰器能够同时监听多个属性的变化,当这些属性在一次事件(例如:本次是点击事件)中共同变化时,只会触发一次 @Monitor 的回调方法。
4.2 @Monitor 监听实例对象
实践一

import { promptAction } from ‘@kit.ArkUI’ import { JSON } from ‘@kit.ArkTS’ import json from ‘@ohos.util.json’ class UserInfo { name: string age: number address: Address constructor(name: string, age: number, country: string) { this.name = name this.age = age this.address = new Address(country) } } class Address { country: string constructor(country: string) { this.country = country } } @Entry @ComponentV2 struct Index { @Local userInfo: UserInfo = new UserInfo(“孙膑”, 29, “齐国”) @Monitor(‘userInfo’) onUserInfoChange(monitor: IMonitor) { monitor.dirty.forEach((path) => { promptAction.showToast({ message: ${path}, before:${JSON.stringify(monitor.value(path)?.before!)}, now:${JSON.stringify(monitor.value(path)?.now!)}, alignment: Alignment.Center }) }) } build() { Column({space: 20}) { Row() { Text(name:${this.userInfo.name}) Text(age:${this.userInfo.age}) Text(country:${this.userInfo.address.country}) } .justifyContent(FlexAlign.SpaceAround) .width(‘100%’) Button(‘更新实例对象’) .onClick(() => { const name = this.userInfo.name == “孙膑” ? “庞涓” : “孙膑” const country = this.userInfo.address.country == “齐国” ? “魏国” : “齐国” this.userInfo = new UserInfo(name, this.userInfo.age++, country) }) Button(‘更新属性’) .onClick(() => { const name = this.userInfo.name == “孙膑” ? “庞涓” : “孙膑” this.userInfo.name = name this.userInfo.age++ }) } .width(‘100%’) } } ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img- home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fp0-xtjj- private.juejin.cn%2Ftos- cn-i-73owjymdk6%2Fad62b46d85e44b29806b588116860fb0~tplv-73owjymdk6-jj- mark-v1%3A0%3A0%3A0%3A0%3A5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5pyI5pyq5aSu%3Aq75.awebp%3Fpolicy%3DeyJ2bSI6MywidWlkIjoiMTY3OTcwOTQ5NDg0OTYyMyJ9%26rk3s%3Df64ab15b%26x-orig- authkey%3Df32326d3454f2ac7e96d3d06cdbb035152127018%26x-orig- expires%3D1742666482%26x-orig- sign%3DfCtolCtlv6GYuGFKwuGftLt12kk%253D&pos_id=img-bGuxOu8m-1742061703387) @Monitor 装饰实例对象,支持对象的重新赋值。但无法监听实例的属性变化。想要监听属性的变化,需要结合@ObserverV2@Trace来使用。

实践二

import { promptAction } from ‘@kit.ArkUI’ import { JSON } from ‘@kit.ArkTS’ import json from ‘@ohos.util.json’ @ObservedV2 class UserInfo { @Trace name: string age: number address: Address constructor(name: string, age: number, country: string) { this.name = name this.age = age this.address = new Address(country) } } class Address { country: string constructor(country: string) { this.country = country } } @Entry @ComponentV2 struct Index { @Local userInfo: UserInfo = new UserInfo(“孙膑”, 29, “齐国”) @Local user2Info: UserInfo = new UserInfo(“赵云”, 25, “蜀国”) @Monitor(‘userInfo.name’) onUserNameChange(monitor: IMonitor) { monitor.dirty.forEach((path) => { promptAction.showToast({ message: ${path}, before:${JSON.stringify(monitor.value(path)?.before!)}, now:${JSON.stringify(monitor.value(path)?.now!)}, alignment: Alignment.Center }) }) } build() { Column({space: 20}) { Row() { Text(name:${this.userInfo.name}) Text(age:${this.userInfo.age}) Text(country:${this.userInfo.address.country}) } .justifyContent(FlexAlign.SpaceAround) .width(‘100%’) Row() { Text(name:${this.user2Info.name}) Text(age:${this.user2Info.age}) Text(country:${this.user2Info.address.country}) } .justifyContent(FlexAlign.SpaceAround) .width(‘100%’) Button(‘更新实例对象’) .onClick(() => { const name = this.userInfo.name == “孙膑” ? “庞涓” : “孙膑” this.userInfo.name = name }) Button(‘更新实例对象: user2’) .onClick(() => { const u2Name = this.user2Info.name == “赵云” ? “周瑜” : “赵云” this.user2Info.name = u2Name }) } .width(‘100%’) } } ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img- home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fp0-xtjj- private.juejin.cn%2Ftos- cn-i-73owjymdk6%2F5c573251404345d494796e5e06fb04c1~tplv-73owjymdk6-jj- mark-v1%3A0%3A0%3A0%3A0%3A5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5pyI5pyq5aSu%3Aq75.awebp%3Fpolicy%3DeyJ2bSI6MywidWlkIjoiMTY3OTcwOTQ5NDg0OTYyMyJ9%26rk3s%3Df64ab15b%26x-orig- authkey%3Df32326d3454f2ac7e96d3d06cdbb035152127018%26x-orig- expires%3D1742666482%26x-orig- sign%3DaHG7QTJ%252FbXAiLIL%252FUC70h%252BPTts8%253D&pos_id=img- NhMRgXy3-1742061703387) 关键点:

  • @Monitor 结合@ObserverV2@Trace可以监听到属性的变化。在@Monitor 修饰的对象写法是userInfo.name ,其他属性以此类推。
  • 在组件中@Monitor 监听的是指定实例对象的属性,不能监听所有创建的对象的指定属性。所以,这里的 user2Info 对象变更name 属性后并没有触发 @Monitor 的监听回调。有点类似iOS的KVO用法。
实践三

import { promptAction } from ‘@kit.ArkUI’ import { JSON } from ‘@kit.ArkTS’ import json from ‘@ohos.util.json’ @ObservedV2 class UserInfo { @Trace name: string age: number address: Address constructor(name: string, age: number, country: string) { this.name = name this.age = age this.address = new Address(country) } @Monitor(’name’) onAgeChange(monitor: IMonitor) { monitor.dirty.forEach((path) => { console.log(${path}, from:${monitor.value(path)?.before}, now:${monitor.value(path)?.now}) promptAction.showToast({ message: In Class: ${path}, before:${JSON.stringify(monitor.value(path)?.before!)}, now:${JSON.stringify(monitor.value(path)?.now!)}, alignment: Alignment.Center }) }) } } class Address { country: string constructor(country: string) { this.country = country } } @Entry @ComponentV2 struct Index { @Local userInfo: UserInfo = new UserInfo(“孙膑”, 29, “齐国”) @Local user2Info: UserInfo = new UserInfo(“赵云”, 25, “蜀国”) @Monitor(‘userInfo.age’) onUserNameChange(monitor: IMonitor) { monitor.dirty.forEach((path) => { console.log(${path}, from:${monitor.value(path)?.before}, now:${monitor.value(path)?.now}) promptAction.showToast({ message: ${path}, before:${JSON.stringify(monitor.value(path)?.before!)}, now:${JSON.stringify(monitor.value(path)?.now!)}, alignment: Alignment.Center }) }) } build() { Column({space: 20}) { Row() { Text(name:${this.userInfo.name}) Text(age:${this.userInfo.age}) Text(country:${this.userInfo.address.country}) } .justifyContent(FlexAlign.SpaceAround) .width(‘100%’) Row() { Text(name:${this.user2Info.name}) Text(age:${this.user2Info.age}) Text(country:${this.user2Info.address.country}) } .justifyContent(FlexAlign.SpaceAround) .width(‘100%’) Button(‘更新实例对象user1的name’) .onClick(() => { const name = this.userInfo.name == “孙膑” ? “庞涓” : “孙膑” this.userInfo.name = name }) Button(‘更新实例对象user2的name’) .onClick(() => { const u2Name = this.user2Info.name == “赵云” ? “周瑜” : “赵云” this.user2Info.name = u2Name }) Button(‘更新实例对象属性: age’) .onClick(() => { this.userInfo.age = this.userInfo.age + 1 }) } .width(‘100%’) } } ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img- home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fp0-xtjj- private.juejin.cn%2Ftos- cn-i-73owjymdk6%2Fb4a9af39508b4a1e99929e5a8934d918~tplv-73owjymdk6-jj- mark-v1%3A0%3A0%3A0%3A0%3A5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5pyI5pyq5aSu%3Aq75.awebp%3Fpolicy%3DeyJ2bSI6MywidWlkIjoiMTY3OTcwOTQ5NDg0OTYyMyJ9%26rk3s%3Df64ab15b%26x-orig- authkey%3Df32326d3454f2ac7e96d3d06cdbb035152127018%26x-orig- expires%3D1742666482%26x-orig- sign%3DAuoFU6IQiwkGaklXZRDFLkQDoOI%253D&pos_id=img-0oUppMxN-1742061703387) 关键点:

  • @Monitor 可以在使用@ObserverV2装饰的类中使用,在属性被@Trace装饰后,任意对象的改属性变化后,在类中都会收到监听回调。具有收拢归一的效果。
  • 在类被@ObserverV2装饰后,属性未被@Trace装饰,即使变量值变换也会无法触发监听回调。
实践四

import { promptAction } from ‘@kit.ArkUI’ import { JSON } from ‘@kit.ArkTS’ import json from ‘@ohos.util.json’ @ObservedV2 class People { @Trace name: string constructor(name: string) { this.name = name } @Monitor(’name’) onNameChange(monitor: IMonitor) { monitor.dirty.forEach((path) => { console.log(In People ${path}, before:${monitor.value(path)?.before}, now:${monitor.value(path)?.now}) }) } } @ObservedV2 class Student extends People { studentId: string gpa: number constructor(studentId: string, name: string, gpa: number) { super(name) this.studentId = studentId this.gpa = gpa } @Monitor(’name’) onNameChange(monitor: IMonitor) { monitor.dirty.forEach((path) => { console.log(In Student ${path}, before:${monitor.value(path)?.before}, now:${monitor.value(path)?.now}) }) } } @Entry @ComponentV2 struct Index { @Local stu1: Student = new Student(“123”, “李雷”, 4.1) @Local stu2: Student = new Student(“231”, “韩梅梅”, 4.9) build() { Column({space: 20}) { Row() { Text(name:${this.stu1.name}) Text(gpa:${this.stu1.gpa}) } .justifyContent(FlexAlign.SpaceAround) .width(‘100%’) Row() { Text(name:${this.stu2.name}) Text(gpa:${this.stu2.gpa}) } .justifyContent(FlexAlign.SpaceAround) .width(‘100%’) Button(‘更新姓名’) .onClick(() => { this.stu1.name = (this.stu1.name == “李雷”) ? “小明” : “李雷” this.stu2.name = (this.stu2.name == “韩梅梅”) ? “小红” : “韩梅梅” }) } .width(‘100%’) } } ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img- home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fp0-xtjj- private.juejin.cn%2Ftos- cn-i-73owjymdk6%2F803b227179ef4aaf863e6e880d8c609c~tplv-73owjymdk6-jj- mark-v1%3A0%3A0%3A0%3A0%3A5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5pyI5pyq5aSu%3Aq75.awebp%3Fpolicy%3DeyJ2bSI6MywidWlkIjoiMTY3OTcwOTQ5NDg0OTYyMyJ9%26rk3s%3Df64ab15b%26x-orig- authkey%3Df32326d3454f2ac7e96d3d06cdbb035152127018%26x-orig- expires%3D1742666482%26x-orig- sign%3DpvEl43y4XRDrz9AZPfsyXT8KcK0%253D&pos_id=img-iXQyGoH4-1742061703387) // 第一次点击 03-15 09:00:13.126 3494-3494 A03d00/JSAPP com.examp…lication I In People name, before:李雷, now:小明 03-15 09:00:13.127 3494-3494 A03d00/JSAPP com.examp…lication I In Student name, before:李雷, now:小明 03-15 09:00:13.127 3494-3494 A03d00/JSAPP com.examp…lication I In People name, before:韩梅梅, now:小红 03-15 09:00:13.127 3494-3494 A03d00/JSAPP com.examp…lication I In Student name, before:韩梅梅, now:小红 // 第二次点击 03-15 09:00:16.613 3494-3494 A03d00/JSAPP com.examp…lication I In People name, before:小明, now:李雷 03-15 09:00:16.613 3494-3494 A03d00/JSAPP com.examp…lication I In Student name, before:小明, now:李雷 03-15 09:00:16.613 3494-3494 A03d00/JSAPP com.examp…lication I In People name, before:小红, now:韩梅梅 03-15 09:00:16.613 3494-3494 A03d00/JSAPP com.examp…lication I In Student name, before:小红, now:韩梅梅 关键点:

  • @Monitor 可以在继承场景中监听同一个属性的变化。在属性改变后,会触发父类和子类的监听回调。
4.3 @Monitor 监听容器对象
实践一

@Entry @ComponentV2 struct Index { @Local names: string[] = [“孙膑”, “赵云”, “马超”] @Monitor(’names’) onNamesChange(monitor: IMonitor) { monitor.dirty.forEach((path) => { promptAction.showToast({ message: ${path}, before:${monitor.value(path)?.before}, now:${monitor.value(path)?.now}, alignment: Alignment.Center }) }) } build() { Column({space: 20}) { ForEach(this.names, (name: string) => { Text(name:${name}) }) Button(‘更新元素’) .onClick(() => { this.names[0] = ${Date.now().toString()} }) Button(‘更新数组’) .onClick(() => { this.names = [“李雷”, “韩梅梅”, “林涛”] }) } .width(‘100%’) } } ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img- home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fp0-xtjj- private.juejin.cn%2Ftos- cn-i-73owjymdk6%2F50717cc40fc54ecfa2bfa3bf405c3e4c~tplv-73owjymdk6-jj- mark-v1%3A0%3A0%3A0%3A0%3A5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5pyI5pyq5aSu%3Aq75.awebp%3Fpolicy%3DeyJ2bSI6MywidWlkIjoiMTY3OTcwOTQ5NDg0OTYyMyJ9%26rk3s%3Df64ab15b%26x-orig- authkey%3Df32326d3454f2ac7e96d3d06cdbb035152127018%26x-orig- expires%3D1742666482%26x-orig- sign%3DRRbCvEz2ZoLSxCZr3N1n0qWaTGs%253D&pos_id=img-GxIR6RI2-1742061703387) 关键点:

  • @Monitor 装饰数组时:只修改元素不会触发监听回调;重新创建数组对象赋值后才会触发监听回调。
实践二

@ObservedV2 class UserInfo { @Trace name: string @Trace age: number constructor(name: string, age: number) { this.name = name this.age = age } } @Entry @ComponentV2 struct Index { @Local users: UserInfo[] = [new UserInfo(“孙膑”, 29), new UserInfo(“庞涓”, 31)] @Monitor(‘users’) onNamesChange(monitor: IMonitor) { monitor.dirty.forEach((path) => { promptAction.showToast({ message: ${path}, before:${monitor.value(path)?.before}, now:${monitor.value(path)?.now}, alignment: Alignment.Center }) }) } build() { Column({space: 20}) { ForEach(this.users, (user: UserInfo) => { Row() { Text(name:${user.name}) Text(age:${user.age}) } .width(‘100%’) .justifyContent(FlexAlign.SpaceBetween) }) Button(‘更新元素’) .onClick(() => { this.users[0] = new UserInfo(“马超”, 25) }) Button(‘更新数组’) .onClick(() => { this.users = [new UserInfo(“韩梅梅”, 14), new UserInfo(“李雷”, 15)] }) } .width(‘100%’) } } ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img- home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fp0-xtjj- private.juejin.cn%2Ftos- cn-i-73owjymdk6%2F9cf9c568dd5f4d70b52b869d107fb9ba~tplv-73owjymdk6-jj- mark-v1%3A0%3A0%3A0%3A0%3A5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5pyI5pyq5aSu%3Aq75.awebp%3Fpolicy%3DeyJ2bSI6MywidWlkIjoiMTY3OTcwOTQ5NDg0OTYyMyJ9%26rk3s%3Df64ab15b%26x-orig- authkey%3Df32326d3454f2ac7e96d3d06cdbb035152127018%26x-orig- expires%3D1742666482%26x-orig- sign%3DAMUT5zT0Pbe19ZtqyKEpgg9ZLAA%253D&pos_id=img-H7nmSMeU-1742061703387) 关键点:

  • @Monitor 装饰元素是实例对象的数组时:只修改元素不会触发监听回调;重新创建数组对象赋值后才会触发监听回调。
4.4 @Monitor 监听 @Provider对象

在之前的章节中我们介绍过 @Provider@Consumer 装饰器,他们的一大特点是可以使用别名。那么@Monitor 监听的是变量名还是别名呢? @Entry @ComponentV2 struct Index { @Provider(‘xNames’) names: string[] = [“孙膑”, “赵云”, “马超”] @Monitor(’names’) onNamesChange(monitor: IMonitor) { monitor.dirty.forEach((path) => { promptAction.showToast({ message: ${path}, before:${monitor.value(path)?.before}, now:${monitor.value(path)?.now}, alignment: Alignment.Center }) }) } build() { Column({space: 20}) { ForEach(this.names, (name: string) => { Text(name: ${name}) }) Button(‘update array’) .onClick(() => { if (this.names.length > 2) { this.names = [“李雷”, “韩梅梅”] } else { this.names = [“孙膑”, “赵云”, “马超”] } }) } .width(‘100%’) } } @ComponentV2 struct ChildComponent { @Consumer(‘xNames’) cNames: string[] = [] build() { Column({space: 10}) { ForEach(this.cNames, (name: string) => { Text(cName: ${name}) }) } .width(‘100%’) } } ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img- home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fp0-xtjj- private.juejin.cn%2Ftos- cn-i-73owjymdk6%2Fda54f78b1e0944fe97fd9815c07dcef8~tplv-73owjymdk6-jj- mark-v1%3A0%3A0%3A0%3A0%3A5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5pyI5pyq5aSu%3Aq75.awebp%3Fpolicy%3DeyJ2bSI6MywidWlkIjoiMTY3OTcwOTQ5NDg0OTYyMyJ9%26rk3s%3Df64ab15b%26x-orig- authkey%3Df32326d3454f2ac7e96d3d06cdbb035152127018%26x-orig- expires%3D1742666482%26x-orig- sign%3DvRJI9%252BzPUBazmeupUy5rS2RC9Ic%253D&pos_id=img-vTJWEI4I-1742061703387) 关键点:

  • @Monitor 监听@Provider@Consumer 装饰的变量时,监听的是变量名,非别名。
4.5 自定义组件中@Monitor对变量监听的生效及失效时间

页面有页面的生命周期,子组件也有自己的生命周期。那么从哪个时机点可以监听变量,哪个时机点监听会被移除呢? @ObservedV2 class UserInfo { @Trace name: string @Trace age: number constructor(name: string, age: number) { this.name = name this.age = age } } @Entry @ComponentV2 struct Index { @Local showChildComponent: boolean = true @Local userInfo: UserInfo = new UserInfo(“孙膑”, 27) @Monitor(‘userInfo.name’) onNamesChange(monitor: IMonitor) { monitor.dirty.forEach((path) => { console.log(In Fathor monitor: ${path}, before:${JSON.stringify(monitor.value(path)?.before!)}, now:${JSON.stringify(monitor.value(path)?.now!)}) }) } build() { Column({space: 20}) { if (this.showChildComponent) { ChildComponent({cUserInfo: this.userInfo}) } Button(‘change name’) .onClick(() => { this.userInfo.name = ${Date.now().toString()} }) Button(‘show/hide child component’) .onClick(() => { this.showChildComponent = !this.showChildComponent }) } .width(‘100%’) } } @ComponentV2 struct ChildComponent { @Param cUserInfo: UserInfo = new UserInfo(“xx”, -1) @Monitor(‘cUserInfo.name’) onNamesChange(monitor: IMonitor) { monitor.dirty.forEach((path) => { console.log(In Child monitor:${path}, before:${monitor.value(path)?.before}, now:${monitor.value(path)?.now}) }) } aboutToAppear(): void { console.log(aboutToAppear in child: ${this.cUserInfo.name}) this.cUserInfo.name = “aboutToAppear in child” } aboutToDisappear(): void { console.log(aboutToDisappear in child: ${this.cUserInfo.name}) this.cUserInfo.name = “aboutToDisappear in child” } build() { Column({space: 10}) { Text(cName: ${this.cUserInfo.name}) } .width(‘100%’) } } ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img- home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fp0-xtjj- private.juejin.cn%2Ftos- cn-i-73owjymdk6%2F218c6a698b23413ea959784dfe6e6838~tplv-73owjymdk6-jj- mark-v1%3A0%3A0%3A0%3A0%3A5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5pyI5pyq5aSu%3Aq75.awebp%3Fpolicy%3DeyJ2bSI6MywidWlkIjoiMTY3OTcwOTQ5NDg0OTYyMyJ9%26rk3s%3Df64ab15b%26x-orig- authkey%3Df32326d3454f2ac7e96d3d06cdbb035152127018%26x-orig- expires%3D1742666482%26x-orig- sign%3Dx5Y748kkrZGSKYwHmpsoHAkvZMs%253D&pos_id=img-UUSJCQ6e-1742061703387) 03-15 10:12:11.826 29071-29071 A00000/testTag apppool I Succeeded in loading the content. 03-15 10:12:11.830 29071-29071 A03d00/JSAPP apppool I aboutToAppear in child: 孙膑 03-15 10:12:11.830 29071-29071 A03d00/JSAPP apppool I In Fathor monitor: userInfo.name, before:“孙膑”, 03-15 10:12:11.830 29071-29071 A03d00/JSAPP apppool I now:“aboutToAppear in child” 03-15 10:12:11.830 29071-29071 A03d00/JSAPP apppool I In Child monitor:cUserInfo.name, before:孙膑, now:aboutToAppear in child 03-15 10:12:23.669 29071-29071 A03d00/JSAPP com.examp…lication I In Fathor monitor: userInfo.name, before:“aboutToAppear in child”, 03-15 10:12:23.669 29071-29071 A03d00/JSAPP com.examp…lication I now:“1742004743669” 03-15 10:12:23.669 29071-29071 A03d00/JSAPP com.examp…lication I In Child monitor:cUserInfo.name, before:aboutToAppear in child, now:1742004743669 03-15 10:12:29.272 29071-29071 A03d00/JSAPP com.examp…lication I aboutToDisappear in child: 1742004743669 03-15 10:12:29.272 29071-29071 A03d00/JSAPP com.examp…lication I In Fathor monitor: userInfo.name, before:“1742004743669”, 03-15 10:12:29.272 29071-29071 A03d00/JSAPP com.examp…lication I now:“aboutToDisappear in child” 03-15 10:12:29.272 29071-29071 A03d00/JSAPP com.examp…lication I In Child monitor:cUserInfo.name, before:1742004743669, now:aboutToDisappear in child 03-15 10:12:34.398 29071-29071 A03d00/JSAPP com.examp…lication I In Fathor monitor: userInfo.name, before:“aboutToDisappear in child”, 03-15 10:12:34.398 29071-29071 A03d00/JSAPP com.examp…lication I now:“1742004754398” 拆解:

  • 当创建子组件的时候,在 aboutToAppear 的时候,@Monitor 就会监听变量的变化。 03-15 10:12:11.830 29071-29071 A03d00/JSAPP apppool I aboutToAppear in child: 孙膑 03-15 10:12:11.830 29071-29071 A03d00/JSAPP apppool I In Fathor monitor: userInfo.name, before:“孙膑”, 03-15 10:12:11.830 29071-29071 A03d00/JSAPP apppool I now:“aboutToAppear in child” 03-15 10:12:11.830 29071-29071 A03d00/JSAPP apppool I In Child monitor:cUserInfo.name, before:孙膑, now:aboutToAppear in child
  • 当点击’change name’ 按钮的时候,更改 @Trace 装饰的变量,触发父组件和子组件的监听回调。 03-15 10:12:23.669 29071-29071 A03d00/JSAPP com.examp…lication I In Fathor monitor: userInfo.name, before:“aboutToAppear in child”, 03-15 10:12:23.669 29071-29071 A03d00/JSAPP com.examp…lication I now:“1742004743669” 03-15 10:12:23.669 29071-29071 A03d00/JSAPP com.examp…lication I In Child monitor:cUserInfo.name, before:aboutToAppear in child, now:1742004743669
  • 隐藏子组件的时候,在子组件 aboutToDisappear 中修改@Trace 装饰的变量,会触发父组件和子组件的监听回调 03-15 10:12:29.272 29071-29071 A03d00/JSAPP com.examp…lication I aboutToDisappear in child: 1742004743669 03-15 10:12:29.272 29071-29071 A03d00/JSAPP com.examp…lication I In Fathor monitor: userInfo.name, before:“1742004743669”, 03-15 10:12:29.272 29071-29071 A03d00/JSAPP com.examp…lication I now:“aboutToDisappear in child” 03-15 10:12:29.272 29071-29071 A03d00/JSAPP com.examp…lication I In Child monitor:cUserInfo.name, before:1742004743669, now:aboutToDisappear in child
  • 再次更新属性变量的值,看到只有父组件有监听回调,子组件无监听回调。说明子组件已经移除监听。 03-15 10:12:34.398 29071-29071 A03d00/JSAPP com.examp…lication I In Fathor monitor: userInfo.name, before:“aboutToDisappear in child”, 03-15 10:12:34.398 29071-29071 A03d00/JSAPP com.examp…lication I now:“1742004754398” 关键点:
  1. 监听起点
  • @Monitor 在子组件的 aboutToAppear 生命周期中开始监听变量变化。
  1. 监听范围
  • 父组件和子组件中的 @Monitor 回调均会被触发,便于跨组件状态管理。
  1. 监听终点
  • 子组件销毁后,其 @Monitor 监听被移除,仅父组件的回调会被触发。
  1. 生命周期一致性
  • @Monitor 的监听生命周期与组件的生命周期一致,确保状态管理的准确性和高效性。