当前位置: 代码网 > 科技>操作系统>鸿蒙系统 > OpenHarmony嵌套类对象属性变化:@Observed装饰器和@ObjectLink装饰器

OpenHarmony嵌套类对象属性变化:@Observed装饰器和@ObjectLink装饰器

2024年08月04日 鸿蒙系统 我要评论
# OpenHarmony嵌套类对象属性变化:@Observed装饰器和@ObjectLink装饰器 上文所述的装饰器仅能观察到第一层的变化,但是在实际应用开发中,应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。这就引出了@Observed/@ObjectLink装饰器。 说明: 从API version 9开始,这两个装饰器支持在ArkTS卡片中使用。 ## 概述 @O...

openharmony嵌套类对象属性变化:@observed装饰器和@objectlink装饰器

上文所述的装饰器仅能观察到第一层的变化,但是在实际应用开发中,应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。这就引出了@observed/@objectlink装饰器。

说明:

从api version 9开始,这两个装饰器支持在arkts卡片中使用。

概述

@objectlink和@observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步:

​ ● 被@observed装饰的类,可以被观察到属性的变化;

​ ● 子组件中@objectlink装饰器装饰的状态变量用于接收@observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被@observed装饰的项,或者是class object中的属性,这个属性同样也需要被@observed装饰。

​ ● 单独使用@observed是没有任何作用的,需要搭配@objectlink或者@prop使用。

限制条件

使用@observed装饰class会改变class原始的原型链,@observed和其他类装饰器装饰同一个class可能会带来问题。

装饰器说明

@observed类装饰器 说明
装饰器参数
类装饰器 装饰class。需要放在class的定义前,使用new创建类对象。
@objectlink变量装饰器 说明
装饰器参数
同步类型 不与父组件中的任何类型同步变量。
允许装饰的变量类型 必须为被@observed装饰的class实例,必须指定类型。不支持简单类型,可以使用@prop。支持继承date或者array的class实例,示例见观察变化。@objectlink的属性是可以改变的,但是变量的分配是不允许的,也就是说这个装饰器装饰变量是只读的,不能被改变。
被装饰变量的初始值 不允许。

@objectlink装饰的数据为可读示例。

// 允许@objectlink装饰的数据属性赋值
this.objlink.a= ...
// 不允许@objectlink装饰的数据自身赋值
this.objlink= ...

说明:

@objectlink装饰的变量不能被赋值,如果要使用赋值操作,请使用@prop

​ ● @prop装饰的变量和数据源的关系是是单向同步,@prop装饰的变量在本地拷贝了数据源,所以它允许本地更改,如果父组件中的数据源有更新,@prop装饰的变量本地的修改将被覆盖;

​ ● @objectlink装饰的变量和数据源的关系是双向同步,@objectlink装饰的变量相当于指向数据源的指针。禁止对@objectlink装饰的变量赋值,如果一旦发生@objectlink装饰的变量的赋值,则同步链将被打断。因为@objectlink修饰的变量通过数据源(object)引用来初始化。对于实现双向数据同步的@objectlink,赋值相当于更新父组件中的数组项或者class的属性,typescript/javascript不能实现,会发生运行时报错。

变量的传递/访问规则说明

@objectlink传递/访问 说明
从父组件初始化 必须指定。初始化@objectlink装饰的变量必须同时满足以下场景:- 类型必须是@observed装饰的class。- 初始化的数值需要是数组项,或者class的属性。- 同步源的class或者数组必须是@state,@link,@provide,@consume或者@objectlink装饰的数据。同步源是数组项的示例请参考对象数组。初始化的class的示例请参考嵌套对象
与源对象同步 双向。
可以初始化子组件 允许,可用于初始化常规变量、@state、@link、@prop、@provide

图1 初始化规则图示
file

观察变化和行为表现

观察变化

@observed装饰的类,如果其属性为非简单类型,比如class、object或者数组,也需要被@observed装饰,否则将观察不到其属性的变化。

class classa {
  public c: number;

  constructor(c: number) {
    this.c = c;
  }
}

@observed
class classb {
  public a: classa;
  public b: number;

  constructor(a: classa, b: number) {
    this.a = a;
    this.b = b;
  }
}

以上示例中,classb被@observed装饰,其成员变量的赋值的变化是可以被观察到的,但对于classa,没有被@observed装饰,其属性的修改不能被观察到。

@objectlink b: classb

// 赋值变化可以被观察到
this.b.a = new classa(5)
this.b.b = 5

// classa没有被@observed装饰,其属性的变化观察不到
this.b.a.c = 5

@objectlink:@objectlink只能接收被@observed装饰class的实例,可以观察到:

​ ● 其属性的数值的变化,其中属性是指object.keys(observedobject)返回的所有属性,示例请参考嵌套对象

​ ● 如果数据源是数组,则可以观察到数组item的替换,如果数据源是class,可观察到class的属性的变化,示例请参考对象数组

继承date的class时,可以观察到date整体的赋值,同时可通过调用date的接口setfullyear, setmonth, setdate, sethours, setminutes, setseconds, setmilliseconds, settime, setutcfullyear, setutcmonth, setutcdate, setutchours, setutcminutes, setutcseconds, setutcmilliseconds 更新date的属性。

@observed
class dateclass extends date {
  constructor(args: number | string) {
    super(args)
  }
}

@observed
class classb {
  public a: dateclass;

  constructor(a: dateclass) {
    this.a = a;
  }
}

@component
struct viewa {
  label: string = 'date';
  @objectlink a: dateclass;

  build() {
    column() {
      button(`child increase the day by 1`)
        .onclick(() => {
          this.a.setdate(this.a.getdate() + 1);
        })
      datepicker({
        start: new date('1970-1-1'),
        end: new date('2100-1-1'),
        selected: this.a
      })
    }
  }
}

@entry
@component
struct viewb {
  @state b: classb = new classb(new dateclass('2023-1-1'));

  build() {
    column() {
      viewa({ label: 'date', a: this.b.a })

      button(`parent update the new date`)
        .onclick(() => {
          this.b.a = new dateclass('2023-07-07');
        })
      button(`viewb: this.b = new classb(new dateclass('2023-08-20'))`)
        .onclick(() => {
          this.b = new classb(new dateclass('2023-08-20'));
        })
    }
  }
}

框架行为

​ 1. 初始渲染:

​ a. @observed装饰的class的实例会被不透明的代理对象包装,代理了class上的属性的setter和getter方法

​ b. 子组件中@objectlink装饰的从父组件初始化,接收被@observed装饰的class的实例,@objectlink的包装类会将自己注册给@observed class。

​ 2. 属性更新:当@observed装饰的class属性改变时,会走到代理的setter和getter,然后遍历依赖它的@objectlink包装类,通知数据更新。

使用场景

嵌套对象

以下是嵌套类对象的数据结构。

// objectlinknestedobjects.ets
let nextid: number = 1;

@observed
class classa {
  public id: number;
  public c: number;

  constructor(c: number) {
    this.id = nextid++;
    this.c = c;
  }
}

@observed
class classb {
  public a: classa;

  constructor(a: classa) {
    this.a = a;
  }
}

@observed
class classd {
  public c: classc;

  constructor(c: classc) {
    this.c = c;
  }
}

@observed
class classc extends classa {
  public k: number;

  constructor(k: number) {
    // 调用父类方法对k进行处理
    super(k);
    this.k = k;
  }
}

以下组件层次结构呈现的是嵌套类对象的数据结构。

@component
struct viewc {
  label: string = 'viewc1';
  @objectlink c: classc;

  build() {
    row() {
      column() {
        text(`viewc [${this.label}] this.a.c = ${this.c.c}`)
          .fontcolor('#ffffffff')
          .backgroundcolor('#ff3fc4c4')
          .height(50)
          .borderradius(25)
        button(`viewc: this.c.c add 1`)
          .backgroundcolor('#ff7fcf58')
          .onclick(() => {
            this.c.c += 1;
            console.log('this.c.c:' + this.c.c)
          })
      }
    .width(300)
  }
}
}

@entry
@component
struct viewb {
  @state b: classb = new classb(new classa(0));
  @state child : classd = new classd(new classc(0));
  build() {
    column() {
      viewc({ label: 'viewc #3', c: this.child.c})
      button(`viewc: this.child.c.c add 10`)
        .backgroundcolor('#ff7fcf58')
        .onclick(() => {
          this.child.c.c += 10
          console.log('this.child.c.c:' + this.child.c.c)
        })
    }
  }
}

被@observed装饰的classc类,可以观测到继承基类的属性的变化。

viewb中的事件句柄:

​ ● this.child.c = new classa(0) 和this.b = new classb(new classa(0)): 对@state装饰的变量b和其属性的修改。

​ ● this.child.c.c = ... :该变化属于第二层的变化,@state无法观察到第二层的变化,但是classa被@observed装饰,classa的属性c的变化可以被@objectlink观察到。

viewc中的事件句柄:

​ ● this.c.c += 1:对@objectlink变量a的修改,将触发button组件的刷新。@objectlink和@prop不同,@objectlink不拷贝来自父组件的数据源,而是在本地构建了指向其数据源的引用。

​ ● @objectlink变量是只读的,this.a = new classa(...)是不允许的,因为一旦赋值操作发生,指向数据源的引用将被重置,同步将被打断。

对象数组

对象数组是一种常用的数据结构。以下示例展示了数组对象的用法。

@component
struct viewa {
  // 子组件viewa的@objectlink的类型是classa
  @objectlink a: classa;
  label: string = 'viewa1';

  build() {
    row() {
      button(`viewa [${this.label}] this.a.c = ${this.a.c} +1`)
        .onclick(() => {
          this.a.c += 1;
        })
    }
  }
}

@entry
@component
struct viewb {
  // viewb中有@state装饰的classa[]
  @state arra: classa[] = [new classa(0), new classa(0)];

  build() {
    column() {
      foreach(this.arra,
        (item: classa) => {
          viewa({ label: `#${item.id}`, a: item })
        },
        (item: classa): string => item.id.tostring()
      )
      // 使用@state装饰的数组的数组项初始化@objectlink,其中数组项是被@observed装饰的classa的实例
      viewa({ label: `viewa this.arra[first]`, a: this.arra[0] })
      viewa({ label: `viewa this.arra[last]`, a: this.arra[this.arra.length-1] })

      button(`viewb: reset array`)
        .onclick(() => {
          this.arra = [new classa(0), new classa(0)];
        })
      button(`viewb: push`)
        .onclick(() => {
          this.arra.push(new classa(0))
        })
      button(`viewb: shift`)
        .onclick(() => {
          this.arra.shift()
        })
      button(`viewb: chg item property in middle`)
        .onclick(() => {
          this.arra[math.floor(this.arra.length / 2)].c = 10;
        })
      button(`viewb: chg item property in middle`)
        .onclick(() => {
          this.arra[math.floor(this.arra.length / 2)] = new classa(11);
        })
    }
  }
}


​ ● this.arra[math.floor(this.arra.length/2)] = new classa(..) :该状态变量的改变触发2次更新:

​ ○ foreach:数组项的赋值导致foreach的itemgenerator被修改,因此数组项被识别为有更改,foreach的item builder将执行,创建新的viewa组件实例。

​ ○ viewa({ label: viewa this.arra[first], a: this.arra[0] }):上述更改改变了数组中第一个元素,所以绑定this.arra[0]的viewa将被更新。

​ ● this.arra.push(new classa(0)) : 将触发2次不同效果的更新:

​ ○ foreach:新添加的classa对象对于foreach是未知的itemgenerator,foreach的item builder将执行,创建新的viewa组件实例。

​ ○ viewa({ label: viewa this.arra[last], a: this.arra[this.arra.length-1] }):数组的最后一项有更改,因此引起第二个viewa的实例的更改。对于viewa({ label: viewa this.arra[first], a: this.arra[0] }),数组的更改并没有触发一个数组项更改的改变,所以第一个viewa不会刷新。

​ ● this.arra[math.floor(this.arra.length/2)].c:@state无法观察到第二层的变化,但是classa被@observed装饰,classa的属性的变化将被@objectlink观察到。

二维数组

使用@observed观察二维数组的变化。可以声明一个被@observed装饰的继承array的子类。

@observed
class stringarray extends array<string> {
}


使用new stringarray()来构造stringarray的实例,new运算符使得@observed生效,@observed观察到stringarray的属性变化。

声明一个从array扩展的类class stringarray extends array<string> {},并创建stringarray的实例。@observed装饰的类需要使用new运算符来构建class实例。

@observed
class stringarray extends array<string> {
}

@component
struct itempage {
  @objectlink itemarr: stringarray;

  build() {
    row() {
      text('itempage')
        .width(100).height(100)

      foreach(this.itemarr,
        (item: string | resource) => {
          text(item)
            .width(100).height(100)
        },
        (item: string) => item
      )
    }
  }
}

@entry
@component
struct indexpage {
  @state arr: array<stringarray> = [new stringarray(), new stringarray(), new stringarray()];

  build() {
    column() {
      itempage({ itemarr: this.arr[0] })
      itempage({ itemarr: this.arr[1] })
      itempage({ itemarr: this.arr[2] })
      divider()


      foreach(this.arr,
        (itemarr: stringarray) => {
          itempage({ itemarr: itemarr })
        },
        (itemarr: string) => itemarr[0]
      )

      divider()

      button('update')
        .onclick(() => {
          console.error('update all items in arr');
          if ((this.arr[0] as array<string>)[0] !== undefined) {
            // 正常情况下需要有一个真实的id来与foreach一起使用,但此处没有
            // 因此需要确保推送的字符串是唯一的。
            this.arr[0].push(`${this.arr[0].slice(-1).pop()}${this.arr[0].slice(-1).pop()}`);
            this.arr[1].push(`${this.arr[1].slice(-1).pop()}${this.arr[1].slice(-1).pop()}`);
            this.arr[2].push(`${this.arr[2].slice(-1).pop()}${this.arr[2].slice(-1).pop()}`);
          } else {
            this.arr[0].push('hello');
            this.arr[1].push('world');
            this.arr[2].push('!');
          }
        })
    }
  }
}

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com