鸿蒙中级课程笔记2—状态管理V2—@ObservedV2装饰器和@Trace装饰器:类属性变化观测

为了增强状态管理框架对类对象中属性的观测能力,开发者可以使用@ObservedV2装饰器和@Trace装饰器装饰类以及类中的属性。

@ObservedV2和@Trace提供了对嵌套类对象属性变化直接观测的能力,是状态管理V2中相对核心的能力之一。在阅读本文档前,建议提前阅读:状态管理概述来了解状态管理V2整体的能力架构。

说明

@ObservedV2与@Trace装饰器从API version 12开始支持。

从API version 12开始,@ObservedV2与@Trace装饰器支持在ArkTS卡片中使用。

从API version 12开始,@ObservedV2与@Trace装饰器支持在元服务中使用。

概述

@ObservedV2装饰器与@Trace装饰器用于装饰类以及类中的属性,使得被装饰的类和属性具有深度观测的能力:

  • @ObservedV2装饰器与@Trace装饰器需要配合使用,单独使用@ObservedV2装饰器或@Trace装饰器没有任何作用。
  • 被@Trace装饰器装饰的属性property变化时,仅会通知property关联的组件进行刷新。
  • 在嵌套类中,嵌套类中的属性property被@Trace装饰且嵌套类被@ObservedV2装饰时,才具有触发UI刷新的能力。
  • 在继承类中,父类或子类中的属性property被@Trace装饰且该property所在类被@ObservedV2装饰时,才具有触发UI刷新的能力。
  • 未被@Trace装饰的属性用在UI中无法感知到变化,也无法触发UI刷新。
  • 使用@ObservedV2与@Trace装饰器的类,需通过new操作符实例化后,才具备被观测变化的能力。

状态管理V1版本对嵌套类对象属性变化直接观测的局限性

现有状态管理V1版本无法实现对嵌套类对象属性变化的直接观测。

V1版本的解决方案是使用@ObjectLink装饰器与自定义组件来实现观测。通过这种方式虽然能够实现对嵌套类中属性变化的观测,但是当嵌套层级较深时,代码将会变得十分复杂,易用性差。因此推出类装饰器@ObservedV2与成员变量装饰器@Trace,增强对嵌套类中属性变化的观测能力。

装饰器说明

@ObservedV2类装饰器 说明
装饰器参数 无。
类装饰器 装饰class。需要放在class的定义前,使用new创建类对象。
@Trace成员变量装饰器 说明
装饰器参数 无。
可装饰的变量 class中成员属性。属性的类型可以为number、string、boolean、class、ArrayDateMapSet等类型。

观察变化

例子参考@ObservedV2装饰器和@Trace装饰器:类属性变化观测

使用@ObservedV2装饰的类中被@Trace装饰的属性具有被观测变化的能力,当该属性值变化时,会触发该属性绑定的UI组件刷新。

  • 在嵌套类中使用@Trace装饰的属性具有被观测变化的能力。

  • 在继承类中使用@Trace装饰的属性具有被观测变化的能力。

  • 类中使用@Trace装饰的静态属性具有被观测变化的能力。

  • @Trace装饰内置类型时,可以观测各自API导致的变化:

    类型 可观测变化的API
    Array push、pop、shift、unshift、splice、copyWithin、fill、reverse、sort
    Date setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds
    Map set, clear, delete
    Set add, clear, delete

使用限制

@ObservedV2与@Trace装饰器存在以下使用限制:

  • 非@Trace装饰的成员属性用在UI上无法触发UI刷新。
  • @ObservedV2仅能装饰class,无法装饰自定义组件,否则编译时报错。
  • @Trace不能用在没有被@ObservedV2装饰的class上,否则编译时报错。
  • @Trace是class中属性的装饰器,不能用在struct中,否则编译时报错。
  • @ObservedV2、@Trace不能与@Observed@Track混合使用,否则编译时报错。
  • 使用@ObservedV2与@Trace装饰的类不能和@State等V1的装饰器混合使用,否则编译时报错。
  • 继承自@ObservedV2的类无法和@State等V1的装饰器混用,否则运行时报错。
  • 使用@ObservedV2与@Trace装饰器的类,需通过new操作符实例化后,才具备被观测变化的能力。
  • @ObservedV2的类实例无法直接使用JSON.parse反序列化获得(直接使用JSON.parse反序列化获得的对象无法观察属性变化),可搭配三方库class-transformer实现反序列化后可观察,示例请参考@ObservedV2装饰对象的序列化与反序列化

使用场景

例子参考@ObservedV2装饰器和@Trace装饰器:类属性变化观测

嵌套类场景

@Trace装饰器与现有状态管理框架的@Track@State装饰器的能力不同,@Track使class具有属性级更新的能力,但并不具备深度观测的能力;而@State只能观测到对象本身以及第一层的变化,对于多层嵌套场景只能通过封装自定义组件,搭配@Observed@ObjectLink来实现观测。

继承类场景

@Trace支持在类的继承场景中使用,无论是在基类还是继承类中,只有被@Trace装饰的属性才具有被观测变化的能力。

@Trace装饰基础类型的数组

@Trace装饰数组时,使用支持的API能够观测到变化。支持的API见观察变化

@Trace装饰对象数组

  • @Trace装饰对象数组personList以及Person类中的age属性,因此当personList、age改变时均可以观测到变化。

@Trace装饰Map类型

  • 被@Trace装饰的Map类型属性可以观测到调用API带来的变化,包括 set、clear、delete。

@Trace装饰Set类型

  • 被@Trace装饰的Set类型属性可以观测到调用API带来的变化,包括 add、clear和delete。

@Trace装饰Date类型

  • @Trace装饰的Date类型属性可以观测调用API带来的变化,包括 setFullYear、setMonth、setDate、setHours、setMinutes、setSeconds、setMilliseconds、setTime、setUTCFullYear、setUTCMonth、setUTCDate、setUTCHours、setUTCMinutes、setUTCSeconds、setUTCMilliseconds。

常见问题

@ObservedV2装饰对象的序列化与反序列化

@ObservedV2装饰的对象序列化后会为@Trace装饰的属性添加__ob_前缀。

TypeScript 复制代码
@ObservedV2
class Info {
  @Trace name: string = 'Tom';
  @Trace age: number = 24;
}

let realInfo: Info = new Info();
let jsonResult: string = JSON.stringify(realInfo); // '{"__ob_name":"Tom","__ob_age":24}'

将@ObservedV2装饰的对象通过JSON.stringify序列化后,再通过JSON.parse反序列化,将失去观察能力。

TypeScript 复制代码
@ObservedV2
class Info {
  @Trace name: string = 'Tom';
  @Trace age: number = 24;
}

let realInfo: Info = new Info();
let jsonResult: string = JSON.stringify(realInfo); // '{"__ob_name":"Tom","__ob_age":24}'
let parseInfo: Info = JSON.parse(jsonResult);

// 与直接通过new操作符创建的对象不同,JSON.parse获得的对象实际并不是Info的实例,所以无属性观察能力
let isInfoByNew: boolean = realInfo instanceof Info; // true
let isInfoByParse: boolean = parseInfo instanceof Info; // false

可以配合三方库class-transformer实现反序列化后可观察。

class-transformer可以通过如下命令安装。

  1. ohpm install class-transformer
复制代码
使用举例如下:
TypeScript 复制代码
import { plainToInstance } from 'class-transformer'; // 导入三方库
@ObservedV2
class Info {
  @Trace name: string = 'Tom';
  @Trace age: number = 24;
}
let realInfo: Info = new Info();
let jsonResult: string = JSON.stringify(realInfo); // '{"__ob_name":"Tom","__ob_age":24}'
let parseInfo: Info = JSON.parse(jsonResult);

let transformedInfo: Info = plainToInstance(Info, parseInfo);
let isInfoByTransformed: boolean = transformedInfo instanceof Info; // true

若为多层对象嵌套场景,需要进行额外处理,包括:

  • 去除序列化结果中的__ob_前缀,否则内层对象无法被正确转换。
  • 使用class-transformer库中提供的@Type装饰器(为与状态管理V2的@Type装饰器区分,示例中重命名为TypeFromLibrary)标记里层对象的类型。

使用三方库的@Type装饰器需要安装reflect-metadata

reflect-metadata可以通过如下命令安装。

  1. ohpm install reflect-metadata@0.2.1
TypeScript 复制代码
import { plainToInstance, Type as TypeFromLibrary} from 'class-transformer'; // 导入三方库
import 'reflect-metadata'; // 三方库的@Type装饰器需要使用
@ObservedV2
class Info {
  @Trace name: string = 'Tom';
  @Trace age: number = 24;
}
@ObservedV2
class InfoWrapper {
  // 使用三方库的@Type装饰器(重命名为TypeFromLibrary)标记内层属性的类型
  @TypeFromLibrary(() => Info)
  @Trace info: Info = new Info();
}
let realWrapper: InfoWrapper = new InfoWrapper();
let infoWrapperJson: string = JSON.stringify(realWrapper); // '{"__ob_info":{"__ob_name":"Tom","__ob_age":24}}'
// 去除属性key的'__ob_'前缀,此处仅做演示,开发者需根据实际类型定义情况完成去除key中的'__ob_'前缀
let jsonHandled = infoWrapperJson.replaceAll('__ob_', ''); // '{"info":{"name":"Tom","age":24}}'
let wrapperHandled = plainToInstance(InfoWrapper, JSON.parse(jsonHandled));

let isWrapper: boolean = wrapperHandled instanceof InfoWrapper; // true
let isInfo: boolean = (wrapperHandled.info) instanceof Info; // true

在UI中使用的完整示例如下。

TypeScript 复制代码
import { plainToInstance, Type as TypeFromLibrary } from 'class-transformer'; // 导入三方库
import 'reflect-metadata'; // 三方库的@Type装饰器需要使用

// 模拟json键值对对象
let testJSON: Record<string, ESObject> = {
  'id': 1,
  'info': {
    'name': 'Tom',
    'age': 24
  },
  'friends': [
    {
      'name': 'John',
      'age': 23
    },
    {
      'name': 'Mary',
      'age': 24
    }
  ]
}

@ObservedV2
class Info {
  @Trace public name?: string;
  @Trace public age?: number;
}

@ObservedV2
class Person {
  public id?: number;
  // 使用三方库的@Type装饰器(重命名为TypeFromLibrary)标记内层属性的类型
  @TypeFromLibrary(() => Info)
  @Trace public info?: Info;
  // 使用三方库的@Type装饰器(重命名为TypeFromLibrary)标记内层属性的类型
  @TypeFromLibrary(() => Info)
  @Trace public friends?: Info[];
}

@Entry
@ComponentV2
struct SerializationAndDeserialization {
  @Local person: Person | undefined = undefined;
  aboutToAppear(): void {
    this.person = plainToInstance(Person, testJSON); // 直接将对象通过plainToInstance转为Person实例
  }

  build() {
    Column() {
      Text(`name: ${this.person?.info?.name}, age: ${this.person?.info?.age}`)
        .onClick(() => {
          if (this.person?.info?.age) {
            this.person!.info!.age++; // 修改可观察
          }
        })
      ForEach(this.person?.friends, (item: Info) => {
        Text(`friend name: ${item.name}, age: ${item.age}`)
          .onClick(() => {
            if (item.age) {
              item.age++; // 修改可观察
            }
          })
      })

      Button('Refresh Info')
        .onClick(() => {
          let json: string =
            `{
              "id":12,
                "__ob_info":
                  {
                    "__ob_name":"Jimmy",
                    "__ob_age":35
                   },
              "__ob_friends":[
                {
                  "__ob_name":"Bob",
                  "__ob_age":30
                },
                {
                  "__ob_name":"Kevin",
                  "__ob_age":33
                }
              ]
            }`;
          // 去除'__ob_'前缀后通过JSON.parse与plainToInstance将json字符串转化成Person对象
          this.person = plainToInstance(Person, JSON.parse(json.replaceAll('__ob_', '')));
        })
    }
  }
}

router传递的@ObservedV2类型显示异常

用router传递的@ObservedV2类,由于经过序列化生成的属性名称与类中的原始属性名称不一致,不能直接通过as类型转换成@ObservedV2的实例,需要反序列化重新生成@ObservedV2实例。反序列化相关内容请参考@ObservedV2装饰对象的序列化与反序列化。例子参考@ObservedV2装饰器和@Trace装饰器:类属性变化观测最后的代码。

相关推荐
航Hang*2 小时前
计算机等级考试(二级WPS)---第1章:综合应用基础---第2节:PDF文件应用
笔记·学习·pdf·wps·计算机二级·计算机等级考试
zhangrelay3 小时前
Linux(ubuntu)如何锁定cpu频率工作在最低能耗模式下
linux·笔记·学习
小风呼呼吹儿3 小时前
Flutter 框架跨平台鸿蒙开发 - 虚拟拼豆图纸查看应用开发教程
flutter·华为·harmonyos
三伏5223 小时前
Cortex-M3权威指南Cn第四、五章——笔记
笔记·cortex-m3
Miguo94well3 小时前
Flutter框架跨平台鸿蒙开发——戒拖延APP的开发流程
flutter·华为·harmonyos·鸿蒙
轴测君3 小时前
MobileNet V1
人工智能·pytorch·笔记
Aliex_git4 小时前
Claude Code 使用笔记(一)- 配置和基础
人工智能·笔记·学习·ai编程
云潮汐表4 小时前
烟台潮汐表查询2026-01-25
笔记
小风呼呼吹儿4 小时前
Flutter 框架跨平台鸿蒙开发 - 虚拟红包雨应用开发教程
flutter·华为·harmonyos