DEV Community

Freerain
Freerain

Posted on

鸿蒙Next中@Observed和@ObjectLink的使用方法

一、概述

在鸿蒙Next中,@Observed@ObjectLink装饰器用于处理嵌套类对象属性变化,实现双向数据同步,弥补了其他装饰器只能观察一层变化的局限。从API version 9开始,这两个装饰器支持在ArkTS卡片中使用,从API version 11开始,支持在元服务中使用。

(一)功能特性

  1. @Observed用于标记类,使其创建的实例能被观察到属性变化,主要用于嵌套类场景。
  2. @ObjectLink装饰子组件中的状态变量,接收@Observed装饰的类的实例,与父组件中对应的状态变量建立双向数据绑定。

二、装饰器说明

(一)@Observed类装饰器

  1. 参数:无。
  2. 类装饰器:用于修饰class,需放在class定义前,通过new创建类对象。

(二)@ObjectLink变量装饰器

  1. 参数:无。
  2. 允许装饰的变量类型
    • 必须为被@Observed装饰的class实例,且必须指定类型。
    • 不支持简单类型,若需使用简单类型可考虑@Prop
    • 支持继承Date、Array的class实例,API11及以上支持继承Map、Set的class实例,API11及以上还支持@Observed装饰类和undefined或null组成的联合类型。
    • 变量属性可改变,但变量自身分配不允许,即该装饰器装饰的变量只读,不能被重新赋值(整体替换除外,但要在父组件进行)。

三、变量的传递/访问规则

  1. 从父组件初始化
    • 必须指定。
    • 初始化@ObjectLink装饰的变量需同时满足以下条件:
      • 类型必须是@Observed装饰的class。
      • 初始化数值需是数组项或class的属性。
      • 同步源的class或数组必须是@State@Link@Provide@Consume或者@ObjectLink装饰的数据。
  2. 与源对象同步:双向同步。
  3. 可以初始化子组件:允许,可用于初始化常规变量、@State@Link@Prop@Provide

四、观察变化和行为表现

(一)观察变化

  1. @Observed装饰的类
    • 若属性为非简单类型(如class、Object或数组),也需被@Observed装饰,否则其属性变化无法观察到。
  2. @ObjectLink装饰的变量
    • 可观察到属性数值的变化(属性指Object.keys返回的所有属性)。
    • 若数据源是数组,可观察到数组item的替换;若数据源是class,可观察到class的属性变化。
    • 对于继承Date、Map、Set的class实例,可分别观察到相应类型的整体赋值及通过接口更新值的操作(如Date的日期属性更新、Map的键值更新、Set的元素更新等)。

(二)框架行为

  1. 初始渲染
    • @Observed装饰的class的实例被不透明代理对象包装,代理其属性的setter和getter方法。
    • 子组件中@ObjectLink装饰的变量从父组件初始化,接收@Observed装饰的class的实例,@ObjectLink的包装类将自己注册给@Observed class
  2. 属性更新
    • @Observed装饰的class属性改变时,执行代理的setter和getter,遍历依赖它的@ObjectLink包装类,通知数据更新。

五、使用场景示例

(一)嵌套对象

  1. 定义了BagUserBookBookName等类,均被@Observed装饰,形成嵌套对象结构。
  2. ViewAViewC子组件中通过@ObjectLink接收父组件ViewB@State变量的属性(如Bag实例或BookName实例),实现双向同步。
  3. ViewB中对user.bagchild.bookName.size等属性的修改,能在相关子组件中同步更新。注意@ObjectLink变量只读,不能进行整体赋值操作(如this.bookName = new bookName(...)),但可更改成员属性(如this.bookName.size += 1)。

(二)对象数组

  1. 定义Info类被@Observed装饰,Parent组件中有@State装饰的Info[]数组。
  2. Child子组件通过@ObjectLink接收Info实例,实现双向同步。
  3. Parent中对数组的操作(如pushshift、修改数组项属性等)会触发不同的更新效果,涉及ForEach的重新执行和相关子组件的刷新,而对于@State无法观察到的第二层属性变化(如Info类中的info属性),由于Info@Observed装饰,可被@ObjectLink观察到并同步更新。

(三)二维数组

  1. 声明StringArray类继承Array<string>并被@Observed装饰,通过new创建实例。
  2. IndexPage组件中定义@State装饰的Array<StringArray>二维数组,ItemPage子组件通过@ObjectLink接收StringArray实例,实现双向同步。
  3. IndexPage中对二维数组元素的操作(如push)会触发相应的UI更新。

(四)继承Map类(API11及以上)

  1. 定义Info类包含MyMap<number, string>类型的属性,MyMap类需被@Observed装饰(此处假设已定义)。
  2. 在相关组件中,通过@ObjectLink实现对MyMap类型数据的双向同步,点击按钮改变MyMap属性时,视图会随之刷新。同理,对于继承Set类也类似(可参考继承Set类部分的示例)。

六、限制条件

  1. 使用@Observed装饰class会改变其原始原型链,与其他类装饰器一起使用可能导致问题。
  2. @ObjectLink装饰器不能在@Entry装饰的自定义组件中使用。
  3. @ObjectLink装饰的变量类型需为显式的被@Observed装饰的类,未指定类型或类型错误在编译期会报错。
  4. @ObjectLink装饰的变量不能本地初始化,只能通过构造参数从父组件传入初始值,否则编译报错。且变量只读,赋值操作会导致运行时报错(除在父组件进行整体替换)。

七、常见问题及注意事项

(一)在子组件中给@ObjectLink装饰的变量赋值

不允许直接赋值,会导致同步链打断和运行时报错,应在父组件进行整体替换或仅修改其属性。

(二)基础嵌套对象属性更改失效

确保嵌套对象中的类及其属性(非简单类型)都被@Observed装饰,否则属性变化无法被观察到。

(三)复杂嵌套对象属性更改失效

如二维数组等复杂嵌套结构,同样要保证各层级相关类都被@Observed装饰,以实现属性变化的观察和同步。

(四)@Prop@ObjectLink的差异

@Prop装饰的变量与数据源单向同步,允许本地更改但父组件数据源更新时本地修改会被覆盖;@ObjectLink装饰的变量与数据源双向同步,相当于指向数据源的指针,只读且不能重新赋值(整体替换在父组件进行)。

(五)在@Observed装饰类的构造函数中延时更改成员变量

不建议在构造函数中进行延时更改成员变量的操作,可能导致预期外的行为,应在合适的时机修改属性以确保变化可被观察和同步。

(六)@ObjectLink数据源更新时机

数据源(@Observed装饰的类实例)的属性变化会触发@ObjectLink装饰变量的更新,从而更新相关UI组件,但要注意同步机制和触发条件,如ForEach的更新机制对数组操作的影响等。

(七)使用a.b(this.object)形式调用,不会触发UI刷新

@ObjectLink装饰的变量是Object类型,且在build方法内通过a.b(this.object)形式调用(如静态方法或组件内部方法修改Object属性)时,无法触发UI刷新。解决方法是先对变量进行赋值,使修改操作作用于带有Proxy代理的变量,从而实现UI刷新。

Top comments (0)