鸿蒙 @Monitor装饰器:状态变量修改监听解析

本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新

一、概述

@Monitor装饰器是HarmonyOS ArkTS开发中用于增强状态变量变化监听能力的重要工具。从API version 12开始支持,该装饰器提供了比传统@Watch装饰器更强大的监听功能,特别适合需要深度监听对象属性变化的场景。

二、核心特性

1. 深度监听能力

@Monitor装饰器能够监听嵌套类、多维数组、对象数组中指定项的变化,支持获取变化前后的值。

2. 严格相等判断

使用严格相等(===)判断属性是否变化,只有在值确实发生变化时才会触发回调。

3. 多属性同时监听

单个@Monitor装饰器可以同时监听多个属性的变化,当这些属性在一次事件中共同变化时,只会触发一次回调。

4. 变化前后值获取

能够获取到变化之前的值和变化之后的值,为复杂业务逻辑提供支持。

三、使用限制和要求

1. 环境要求

  • 需要在@ComponentV2装饰的自定义组件中使用
  • 监听的变量必须被@Local、@Param、@Provider、@Consumer、@Computed等状态变量装饰器装饰
  • 在类中使用时需要与@ObservedV2和@Trace配合使用

2. 监听限制

  • 无法监听内置类型(Array、Map、Date、Set)的API调用引起的变化
  • 当监听整个数组时,更改数组的某一项不会被监听到
  • 需要监听的类属性必须被@Trace装饰

3. 参数限制

  • @Monitor的参数需要为监听属性名的字符串
  • 仅可以使用字符串字面量、const常量、enum枚举值作为参数
  • 如果使用变量作为参数,仅会监听@Monitor初始化时变量值所对应的属性

4. 重复监听限制

  • 不建议在一个类中对同一个属性进行多次@Monitor的监听
  • 当一个类中存在对一个属性的多次监听时,只有最后一个定义的监听方法会生效

5. 循环调用限制

  • 避免在@Monitor中再次更改被监听的属性,这会导致无限循环

四、与@Watch装饰器的对比

特性维度@Watch装饰器@Monitor装饰器
API版本API version 7+API version 12+
监听粒度对象/数组整体变化支持对象属性、数组元素的细粒度监听
值获取能力只能获取变化后的值可获取变化前和变化后的值
使用方式将函数名作为参数传入装饰器直接装饰回调函数
多属性监听不支持,需要为每个属性单独定义@Watch支持同时监听多个属性
深度监听不支持深度监听嵌套对象属性支持深度监听嵌套结构
回调触发每次变化都触发回调同一次事件中的多次变化只触发一次回调
类属性要求无特殊要求需要配合@ObservedV2和@Trace使用
组件要求可在普通@Component中使用必须在@ComponentV2中使用
参数类型函数名字符串属性名字符串,支持字面量、常量、枚举
元服务支持不支持支持
循环风险较低较高,需要避免在回调中修改监听属性

传统@Watch的局限性

@Observed
class UserProfile {
  userName: string = '张三';
  userAge: number = 25;
}

@Component
struct UserInfo {
  @State @Watch('onProfileChange') profile: UserProfile = new UserProfile();
  
  onProfileChange() {
    // 无法知道是哪个属性发生了变化
    // 无法获取变化前的值
  }
}

@Monitor的优势

@ObservedV2
class UserProfile {
  @Trace userName: string = '张三';
  @Trace userAge: number = 25;
}

@ComponentV2
struct UserInfo {
  @Local profile: UserProfile = new UserProfile();
  
  @Monitor('userName', 'userAge')
  onProfileDetailChange(monitor: IMonitor) {
    // 可以精确知道哪个属性发生了变化
    // 可以获取变化前后的值
    monitor.dirty.forEach((path: string) => {
      console.log(`${path} 从 ${monitor.value(path)?.before} 变为 ${monitor.value(path)?.now}`);
    });
  }
}

五、装饰器参数说明

基本语法

@Monitor('property1', 'property2', ...)
onChange(monitor: IMonitor) {
  // 处理变化
}

参数详解

  • 支持字符串类型的对象属性名
  • 可同时监听多个属性,用逗号分隔
  • 支持深层属性路径,如'user.address.city'

六、接口说明

IMonitor接口

提供以下核心方法:

  • dirty: string[] - 发生变化的所有属性路径数组
  • value(path: string): IMonitorValue<T> | undefined - 获取指定路径的变化信息

IMonitorValue<T>接口

包含变化前后值的信息:

  • before: T - 变化前的值
  • now: T - 变化后的值

七、使用示例

1. 基础使用

@Entry
@ComponentV2
struct ProductDetail {
  @Local productName: string = '手机';
  @Local productPrice: number = 3999;
  
  @Monitor('productName', 'productPrice')
  onProductChange(monitor: IMonitor) {
    monitor.dirty.forEach((path: string) => {
      const change = monitor.value(path);
      console.log(`属性 ${path} 从 ${change?.before} 变为 ${change?.now}`);
    });
  }
  
  build() {
    Column() {
      Button('修改信息')
        .onClick(() => {
          this.productName = '平板';
          this.productPrice = 2999;
        })
    }
  }
}

备注:当@Monitor定义在@ComponentV2装饰的自定义组件中时,@Monitor会在状态变量初始化后生效,组件销毁时失效。

2. 监听对象属性

@ObservedV2
class OrderInfo {
  @Trace orderId: string = 'ORD001';
  @Trace status: string = 'pending';
  @Trace amount: number = 100;
}

@Entry
@ComponentV2
struct OrderDetail {
  order: OrderInfo = new OrderInfo();
  
  @Monitor('order.status', 'order.amount')
  onOrderStatusChange(monitor: IMonitor) {
    monitor.dirty.forEach((path: string) => {
      if (path === 'order.status') {
        console.log('订单状态发生变化');
      } else if (path === 'order.amount') {
        console.log('订单金额发生变化');
      }
    });
  }
}

备注:当@Monitor定义在@ObservedV2装饰的类中时,@Monitor会在类的实例创建完成后生效,在类的实例销毁时失效;自定义组件如果被销毁,但是其成员变量order还没有被立刻回收,@Monitor仍然在监听,建议将@Monitor定义在自定义组件中。由于自定义组件在销毁时,框架会手动取消@Monitor的监听(或者主动置空监听的对象)。

3. 监听数组项变化

@ObservedV2
class TaskItem {
  @Trace taskName: string;
  @Trace isCompleted: boolean;
  
  constructor(name: string) {
    this.taskName = name;
    this.isCompleted = false;
  }
}

@Entry
@ComponentV2
struct TaskManager {
  @Local tasks: TaskItem[] = [
    new TaskItem('学习ArkTS'),
    new TaskItem('开发应用')
  ];
  
  @Monitor('tasks[0].isCompleted', 'tasks[1].taskName')
  onTaskChange(monitor: IMonitor) {
    // 处理特定数组项的变化
  }
}

4. 使用const常量作为参数

const USER_NAME = 'userName';
const USER_AGE = 'userAge';

@Entry
@ComponentV2
struct UserInfoComponent {
  @Local userName: string = '张三';
  @Local userAge: number = 25;
  
  @Monitor(USER_NAME, USER_AGE)
  onUserInfoChange(monitor: IMonitor) {
    // 处理用户信息变化
  }
}

八、建议

1. 合理选择监听粒度

  • 对于简单数据类型,可以直接监听
  • 对于复杂对象,建议监听具体属性而不是整个对象
  • 避免过度监听,只监听真正需要的属性

2. 性能考虑

  • 深度监听会增加性能开销,只在必要时使用
  • 对于频繁变化的属性,考虑使用防抖或节流

3. 错误处理

@Monitor('importantData')
onDataChange(monitor: IMonitor) {
  try {
    monitor.dirty.forEach(path => {
      const value = monitor.value(path);
      if (!value) {
        console.error(`获取变化信息失败: ${path}`);
        return;
      }
      // 正常处理变化
    });
  } catch (error) {
    console.error('监听处理出错:', error);
  }
}

4. 避免循环调用

@Monitor('counter')
onCounterChange(monitor: IMonitor) {
  // 错误示例:在回调中修改监听属性会导致无限循环
  // this.counter++;
  
  // 正确做法:只是读取信息,不修改监听属性
  const change = monitor.value('counter');
  console.log(`计数器从 ${change?.before} 变为 ${change?.now}`);
}

九、总结

@Monitor装饰器解决了传统@Watch装饰器在监听精度和数据获取方面的局限性。通过支持深度监听、多属性同时监听、变化前后值获取等功能,为复杂应用的状态管理提供了更加灵活和强大的解决方案。

在选择使用@Monitor还是@Watch时,应该根据具体需求来决定:对于简单的状态监听需求,@Watch足够使用且更轻量;对于复杂的对象结构监听和需要获取变化前后值的场景,@Monitor是更好的选择。

使用时,应该注意@Monitor的各种限制条件,特别是参数类型限制、重复监听限制和循环调用风险,合理选择监听粒度,注意性能影响,以确保应用的稳定性和性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值