本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新
一、概述
@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的各种限制条件,特别是参数类型限制、重复监听限制和循环调用风险,合理选择监听粒度,注意性能影响,以确保应用的稳定性和性能。

被折叠的 条评论
为什么被折叠?



