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 代码演示

typescript 复制代码
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%')
  }
}

关键点:

  • @Monitor 修饰的是字符串类型,其值是对象的变量名
  • @Monitor 装饰的变量不支持动态修改,即不支持在运行时修改。

3.2 拆解代码

3.2.1 IMonitor 接口

IMonitor 是一个接口类型,用于在 @Monitor 的回调函数中获取状态变量变化的信息。它的定义如下:

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

IMonitorValue<T> 是一个泛型接口,用于存储属性变化的信息。它的定义如下:

csharp 复制代码
interface IMonitorValue<T> {
  before: T; // 变化前的值
  now: T;    // 变化后的值
  path: string; // 监听的属性名
}

属性

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

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

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

四、实践探索

4.1 @Monitor 监听基础类型

@Monitor 可以直接修饰基础类型(如 stringnumberbooleanenumnullundefined 等),并在变量变化时更新关联的 UI 组件。

typescript 复制代码
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%')
  }
}

关键点:

  • 非状态变量的更新不会触发 @Monitor 回调。
  • @Monitor 支持修饰 stringnumberbooleanenumnullundefined 等基础类型。
less 复制代码
// 第一次点击
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 监听实例对象

实践一
typescript 复制代码
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%')
  }
}

@Monitor 装饰实例对象,支持对象的重新赋值。但无法监听实例的属性变化。想要监听属性的变化,需要结合@ObserverV2@Trace来使用。

实践二
typescript 复制代码
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%')
  }
}

关键点:

  • @Monitor 结合@ObserverV2@Trace可以监听到属性的变化。在@Monitor 修饰的对象写法是userInfo.name,其他属性以此类推。
  • 在组件中@Monitor 监听的是指定实例对象的属性,不能监听所有创建的对象的指定属性。所以,这里的user2Info 对象变更name 属性后并没有触发 @Monitor 的监听回调。有点类似iOS的KVO用法。
实践三
typescript 复制代码
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%')
  }
}

关键点:

  • @Monitor 可以在使用@ObserverV2装饰的类中使用,在属性被@Trace装饰后,任意对象的改属性变化后,在类中都会收到监听回调。具有收拢归一的效果。
  • 在类被@ObserverV2装饰后,属性未被@Trace装饰,即使变量值变换也会无法触发监听回调。
实践四
typescript 复制代码
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%')
  }
}
less 复制代码
// 第一次点击
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 监听容器对象

实践一
less 复制代码
@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%')
  }
}

关键点:

  • @Monitor 装饰数组时:只修改元素不会触发监听回调;重新创建数组对象赋值后才会触发监听回调。
实践二
typescript 复制代码
@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%')
  }
}

关键点:

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

4.4 @Monitor 监听 @Provider对象

在之前的章节中我们介绍过 @Provider@Consumer 装饰器,他们的一大特点是可以使用别名。那么@Monitor 监听的是变量名还是别名呢?

typescript 复制代码
@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%')
  }
}

关键点:

  • @Monitor 监听@Provider@Consumer 装饰的变量时,监听的是变量名,非别名。

4.5 自定义组件中@Monitor对变量监听的生效及失效时间

页面有页面的生命周期,子组件也有自己的生命周期。那么从哪个时机点可以监听变量,哪个时机点监听会被移除呢?

less 复制代码
@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%')
  }
}
less 复制代码
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 就会监听变量的变化。
less 复制代码
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 装饰的变量,触发父组件和子组件的监听回调。
less 复制代码
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 装饰的变量,会触发父组件和子组件的监听回调
less 复制代码
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
  • 再次更新属性变量的值,看到只有父组件有监听回调,子组件无监听回调。说明子组件已经移除监听。
arduino 复制代码
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 的监听生命周期与组件的生命周期一致,确保状态管理的准确性和高效性。
相关推荐
黄毛火烧雪下2 小时前
ios打包需要的证书及步骤
macos·ios·cocoa
一个处女座的程序猿O(∩_∩)O3 小时前
鸿蒙Next与API 12深度解析:架构、开发实践与代码示例
华为·架构·harmonyos
苏杰豪4 小时前
鸿蒙特效教程04-直播点赞动画效果实现教程
华为·harmonyos
苏杰豪4 小时前
# 鸿蒙特效教程03-水波纹动画效果实现教程
华为·harmonyos
啊是是是4 小时前
HarmonyOS申请用户位置信息授权和再次授权-系统级API获取地理位置的错误码类型问题getCurrentLocation()
harmonyos
追寻向上5 小时前
基于图像比对的跨平台UI一致性校验工具开发全流程指南——Android/iOS/Web三端自动化测试实战
android·ui·ios
MrZWCui5 小时前
iOS OC匹配多个文字修改颜色和字号
学习·macos·ios·objective-c·cocoa·xcode
TARDIS_20206 小时前
OpenHarmony-XTS测试
harmonyos
__Benco6 小时前
OpenHarmony子系统开发 - Rust编译构建指导
开发语言·人工智能·后端·rust·harmonyos