)
@ObservedV2 和 @Trace: 类属性变化观测@ObservedV2装饰器和@Trace装饰器:类属性变化观测状态管理V1 版本无法实现对嵌套类对象属性变化的直接观察,通常需要使用 @ObjectLink 装饰器和自定义组件来实现观察,但当嵌套层级较深时,代码会变得是否复杂,易用性差。@ObservedV2 和 @Trace 提供了对嵌套类对象属性变化的直接观察能力。装饰器装饰器参数装饰的类型@ObservedV2无class@Trace无class 中成员属性,属性的类型可为基本数据类型和 Array、Date、Map、Set 等,不支持 Function 类型@ObservedV2 用于装饰类,@Trace 用于装饰类中的属性,使得被装饰的类和属性具有深度观测的能力:@ObservedV2 和 @Trace 需配合使用,单独使用 @ObservedV2 或 @Trace 没有任何作用被 @Trace 装饰的属性的值变化时,仅会通知与其关联的组件进行刷新;未被 @Trace 装饰的属性值的变化无法触发 UI 刷新使用 @ObservedV2 和 @Trace 装饰的类,需通过 new 实例化后,才具备被观测变化的能力在继承类中,父类或子类中的属性被 @Trace 装饰,且该属性所在的类被 @ObservedV2 装饰时,才具有 UI 刷新的能力在嵌套类中,嵌套类中的属性被 @Trace 装饰且嵌套类被 @Observed 装饰时,才具有触发 UI 刷新的能力@Trace 装饰 Array、Date、Map、Set 的内置类型时,可观测到集合内置类的变化在 @ComponentV2 自定义组件中,被 @ObservedV2 与 @Trace 装饰的变量具有深度观察能力,但对对象整体赋值时,UI 不会自动刷新,状态变量对象需被 @Local 装饰,才可观测对象本身的变化使用限制@ObservedV2 和 @Trace 仅能装饰 class 和属性,不能用在自定义组件中使用ObservedV2/@Trace 有自己独立的观测能力,不仅可在 @ComponentV2 中使用,也可以独立在 @Component 中使用,但不可与 V1 状态管理装饰器混合使用,编译时报错继承自 @ObservedV2 的类无法和 @State 等 V1 的装饰器混合使用,运行时报错@ObservedV2 的类实例无法直接使用 JSON.parse 反序列化获得 (直接使用 JSON.parse 反序列化获得的对象无法观察属性变化,需使用makeObserved方法转换)使用场景嵌套类场景V2 的观测能力绑定在发生变化的属性所在的那个类上,而不是绑定在访问路径的根对象上。嵌套类,只要内层 class 本身被 @ObservedV2 装饰、其属性被 @Trace 装饰,那么无论外层对象是否可观测,该属性的变化都能被追踪到,可触发 UI 刷新。即外层的类可以不被 @ObservedV2 和 @Trace 装饰,但若要观察内层 class 的整体赋值 (outer.inner = new inner()),则需用 @Trace 装饰外层字段本身,才能观测并触发 UI 刷新。// 嵌套类必须被 @ObservedV2 和 @Trace 装饰,才能触发 UI@ObservedV2classUser{@Traceage:number=10;}// 外层的类可以不被 @ObservedV2 和 @Trace 装饰classLogin{user:User=newUser();}@Entry@ComponentV2exportstruct MainPage{// login 的嵌套类 User 被 @ObserveV2 装饰,不可用 @State 等 V1 的装饰器// 即使 login 没有被 @Local 装饰,其 age 属性的改变也会触发 UI 刷新@Locallogin:Login=newLogin();build(){Column({space:20}){Button('change age: '+this.login.user.age).onClick(()={this.login.user.age++})}.width('100%')}}继承类场景@Trace 支持在类的继承场景中使用,无论是在基类还是继承类中,只有被 @Trace 装饰的属性才具有被观测变化的能力。@ObservedV2classUser{// 被 @Trace 装饰,值的变化可触发 UI 刷新@Traceage:number=10;}@ObservedV2classVipextendsUser{// 被 @Trace 装饰,值的变化可触发 UI 刷新@Tracelevel:number=1;// 未被 @Trace 装饰,不会触发 UI 刷新date:string='';}@Entry@ComponentV2exportstruct MainPage{@Localvip:Vip=newVip();build(){Column({space:20}){Button('change age: '+this.vip.age).onClick(()={this.vip.age++})Button('change level: '+this.vip.level).onClick(()={this.vip.level++})}.width('100%')}}@Trace 装饰对象数组@Trace 装饰 Array、Date、Map、Set 的内置类型时,可观测到集合内置类的变化@ObservedV2classUser{// 被 @Trace 装饰,值的变化可触发 UI 刷新@Traceage:number;constructor(age:number){this.age=age;}}@Entry@ComponentV2exportstruct MainPage{// 数组的 userList 的内置类 User 的 age 属性被 @Trace 装饰,其变化可触发 UI 刷新userList:ArrayUser=[newUser(10)];build(){Column({space:20}){Button('change age: '+this.userList[0].age).onClick(()={this.userList[0].age++})}.width('100%')}}@ObservedV2 装饰对象的序列化与反序列化@ObservedV2 装饰的对象序列化后会为 @Trace 装饰的属性添加__ob_前缀@ObservedV2classInfo{@Tracename:string='Tom';@Traceage:number=24;}letrealInfo:Info=newInfo();letjsonResult:string=JSON.stringify(realInfo);// '{"__ob_name":"Tom","__ob_age":24}'由于 @ObservedV2 装饰的对象序列化后会为 @Trace 装饰的属性添加__ob_前缀,再通过 JSON.parse 对其反序列化,将失去观察能力,且得到的并非原对象实例,不能直接通过 as 强制类型转换成原对象。@ObservedV2classInfo{@Tracename:string='Tom';@Traceage:number=24;}letjsonResult:string=JSON.stringify(newInfo());// '{"__ob_name":"Tom","__ob_age":24}'letparseInfo:Info=JSON.parse(jsonResult);// parseInfo 实际并不是 Info 的实例letname:string=parseInfo.name;// parseInfo 的属性名已经变为了 __ob_name,parseInfo.name 为 undefined;在传递 (Router 路由传参、Navigation 组件传参、跨线程通信) 和存储 (Preferences 持久化) 对象时,都会经历 “序列化+反序列化” 的过程如:通过router.pushUrl()参数对象时,参数对象会被序列化后传递,目标页通过router.getParams()拿到的是反序列化后重新生成的对象。// FirstPge 跳转,并传递 @ObservedV2 实例this.getUIContext().getRouter().pushUrl({url:'pages/secondPage',params:this.info;})// secondPage 接收参数// 错误的方式!// 通过 router 传递 @ObservedV2 对象,其在获取后属性名已经改变了,拼接了 __ob_ 前缀,无法通过 as 直接强制转换// this.params 的属性名为 __ob_name,非原始 Info 的实例,不可观测this.params=this.getUIContext().getRouter().getParams()asInfo;为了解决上述问题,可配合三方库 class-transformer 实现反序列,对象的属性名不变,且可观测。安装 class-transformerohpm installclass-transformer【示例】import{plainToInstance}from'class-transformer';// 导入三方库@ObservedV2classInfo{@Tracename:string='Assassin';@Traceage:number=28;}@Entry@ComponentV2exportstruct MainPage{jsonStr:string="{\"name\":\"Assassin\",\"age\":28}";// 通过反序列得到的对象实例 parseInfo,无法观测@LocalparseInfo:Info=JSON.parse(this.jsonStr);// 三方库 class-transformer 实现的反序列化实例,可以被观测@LocaltransformedParseInfo:Info=plainToInstance(Info,this.parseInfo);// 将 Info 序列化后,再返序列化得到的对象@LocalinfoStr:string=JSON.stringify(newInfo());@Localinfo:Info=JSON.parse(this.infoStr)asInfo;// 三方库 class-transformer 将 info 转为可被观察的对象@LocaltransformedInfo:Info=plainToInstance(Info,this.info);aboutToAppear():void{// 接收 router 参数,需先将接收的参数序列化letparamStr:string=JSON.stringify(this.getUIContext().getRouter().getParams());// 反序列化后,通过 plainToInstance 转为可被观察的对象letparams:Info=plainToInstance(Info,JSON.parse(paramStr));}build()