@ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步:
- 被@Observed装饰的类,可以被观察到属性的变化;
- 子组件中@ObjectLink装饰器装饰的状态变量用于接收@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被@Observed装饰。
- 单独使用@Observed是没有任何作用的,需要搭配@ObjectLink或者@Prop使用。
限制条件
- 使用@Observed装饰class会改变class原始的原型链,@Observed和其他类装饰器装饰同一个class可能会带来问题。
- @ObjectLink装饰器不能在@Entry装饰的自定义组件中使用。
装饰器说明
@Observed类装饰器 | 说明 |
---|---|
装饰器参数 | 无 |
类装饰器 | 装饰class。需要放在class的定义前,使用new创建类对象。 |
@ObjectLink变量装饰器 | 说明 |
---|---|
装饰器参数 | 无 |
同步类型 | 不与父组件中的任何类型同步变量。 |
允许装饰的变量类型 | 必须为被@Observed装饰的class实例,必须指定类型。不支持简单类型,可以使用@Prop。@ObjectLink的属性是可以改变的,但是变量的分配是不允许的,也就是说这个装饰器装饰变量是只读的,不能被改变。 |
被装饰变量的初始值 | 不允许。 |
@ObjectLink装饰的数据为可读示例。
说明
@ObjectLink装饰的变量不能被赋值,如果要使用赋值操作,请使用@Prop。
- @Prop装饰的变量和数据源的关系是是单向同步,@Prop装饰的变量在本地拷贝了数据源,所以它允许本地更改,如果父组件中的数据源有更新,@Prop装饰的变量本地的修改将被覆盖;
- @ObjectLink装饰的变量和数据源的关系是双向同步,@ObjectLink装饰的变量相当于指向数据源的指针。如果一旦发生@ObjectLink装饰的变量的赋值,则同步链将被打断。
观察变化和行为表现
观察的变化
@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的属性的变化,示例请参考对象数组。
框架行为
-
初始渲染:
- @Observed装饰的class的实例会被不透明的代理对象包装,代理了class上的属性的setter和getter方法
- 子组件中@ObjectLink装饰的从父组件初始化,接收被@Observed装饰的class的实例,@ObjectLink的包装类会将自己注册给@Observed class。
-
属性更新:当@Observed装饰的class属性改变时,会走到代理的setter和getter,然后遍历依赖它的@ObjectLink包装类,通知数据更新。
嵌套对象
let NextIDx: number = 1
@Observed
class Bag{
id: number
size: number
constructor(size:number) {
this.size = size
this.id = NextIDx++
}
}
@Observed
class User{
bad: Bag
constructor(bag:Bag) {
this.bad = bag
}
}
@Observed
class BookName extends Bag{
nameSize: number
constructor(nameSize: number) {
super(nameSize)
this.nameSize = nameSize
}
}
@Observed
class Book{
bookName: BookName
constructor(bookName:BookName) {
this.bookName = bookName
}
}
@Entry
@Component
struct ObservedPage {
@State user: User = new User(new Bag(0))
@State child: Book = new Book(new BookName(0))
build() {
Column(){
ViewA({
label:'ViewA',
bag:this.user.bad
})
.width(320)
ViewC({
label:'ViewC',
bookName:this.child.bookName
})
.width(320)
Button('ViewC:this.child.bookName.size add 10')
.width(320)
.backgroundColor('#ff7fcf58')
.margin(10)
.onClick(() =>{
this.child.bookName.size += 10
})
Button(`ViewB: this.user.bag = new Bag(10)`)
.width(320)
.backgroundColor('#ff7fcf58')
.margin(10)
.onClick(() => {
this.user.bad = new Bag(10);
})
Button(`ViewB: this.user = new User(new Bag(20))`)
.width(320)
.backgroundColor('#ff7fcf58')
.margin(10)
.onClick(() => {
this.user = new User(new Bag(20));
})
}.width('100%')
.height('100%')
}
}
@Component
struct ViewA{
label: string = 'ViewA'
// 不能本地初始化
@ObjectLink bag: Bag
build(){
Column(){
Text(`ViewA[${this.label}] this.bag.size = ${this.bag.size}`)
.fontColor('#ffffffff')
.backgroundColor('#ff3fc4c4')
.width(320)
.height(50)
.borderRadius(25)
.margin(10)
.textAlign(TextAlign.Center)
Button('ViewA: this.bag.size add 1')
.width(320)
.backgroundColor('#ff7fcf58')
.margin(10)
.onClick(() =>{
this.bag.size += 1
})
}
}
}
@Component
struct ViewC{
label:string = 'ViewC'
@ObjectLink bookName: BookName
build(){
Column(){
Text(`View[${this.label}] this.bookName.size = ${this.bookName.size}}`)
.fontColor('#ffffffff')
.backgroundColor('#ff3fc4c4')
.width(320)
.height(50)
.borderRadius(25)
.margin(10)
.textAlign(TextAlign.Center)
Button(`ViewC: this.bookName.size add 1`)
.width(320)
.backgroundColor('#ff7fcf58')
.margin(10)
.onClick(() => {
this.bookName.size += 1;
console.log('this.bookName.size:' + this.bookName.size)
})
}
.width(320)
}
}
对象数组
let NextID: number = 1
@Observed
class ClassA{
id: number
c: number
constructor(c: number) {
this.id = NextID++
this.c = c
}
}
@Entry
@Component
struct ObservedPage2Page {
// ViewB中有@State装饰的ClassA[]
@State arrA: ClassA[] = [new ClassA(0), new ClassA(0)];
build() {
Column(){
ForEach(this.arrA,(item:ClassA) =>{
ViewA2({label:`#${item.id}`,a:item})
})
// 使用@State装饰的数组的数组项初始化@ObjectLink,其中数组项是被@Observed装饰的ClassA的实例
ViewA2({ label: `ViewA this.arrA[first]`, a: this.arrA[0] })
ViewA2({ label: `ViewA this.arrA[last]`, a: this.arrA[this.arrA.length-1] })
Button(`ViewB: reset array`)
.width(320)
.margin(10)
.onClick(() => {
this.arrA = [new ClassA(0), new ClassA(0)];
})
Button(`ViewB: push`)
.width(320)
.margin(10)
.onClick(() => {
this.arrA.push(new ClassA(0))
})
Button(`ViewB: shift`)
.width(320)
.margin(10)
.onClick(() => {
if (this.arrA.length > 0) {
this.arrA.shift()
} else {
console.log("length <= 0")
}
})
Button(`ViewB: chg item property in middle`)
.width(320)
.margin(10)
.onClick(() => {
this.arrA[Math.floor(this.arrA.length / 2)].c = 10;
})
Button(`ViewB: chg item property in middle`)
.width(320)
.margin(10)
.onClick(() => {
this.arrA[Math.floor(this.arrA.length / 2)] = new ClassA(11);
})
}.width('100%')
.height('100%')
}
}
@Component
struct ViewA2{
@ObjectLink a: ClassA
label: string = "ViewA"
build(){
Column(){
Button(`ViewA [${this.label}] this.a.c = ${this.a ? this.a.c : "undefined"}`)
.width(320)
.margin(10)
.onClick(() => {
this.a.c += 1;
})
}
}