@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化
developer.huawei.com/consumer/cn…
初始化规则图示
说明
@ObjectLink装饰的变量不能被赋值,如果要使用赋值操作,请使用@Prop。
- @Prop装饰的变量和数据源的关系是是单向同步,@Prop装饰的变量在本地拷贝了数据源,所以它允许本地更改,如果父组件中的数据源有更新,@Prop装饰的变量本地的修改将被覆盖;
- @ObjectLink装饰的变量和数据源的关系是双向同步,@ObjectLink装饰的变量相当于指向数据源的指针。禁止对@ObjectLink装饰的变量赋值,如果一旦发生@ObjectLink装饰的变量的赋值,则同步链将被打断。因为@ObjectLink装饰的变量通过数据源(Object)引用来初始化。对于实现双向数据同步的@ObjectLink,赋值相当于更新父组件中的数组项或者class的属性,TypeScript/JavaScript不能实现,会发生运行时报错。
框架行为
-
初始渲染:
- @Observed装饰的class的实例会被不透明的代理对象包装,代理了class上的属性的setter和getter方法
- 子组件中@ObjectLink装饰的class实例,接收被@Observed装饰的class的实例,@ObjectLink的包装类会将自己注册给@Observed class。
-
属性更新:当@Observed装饰的class属性改变时,会走到代理的setter和getter,然后遍历依赖它的@ObjectLink包装类,通知数据更新。
转换前代码
/**
*
* ObjectLinkCmpt.ets
* Created by unravel on 2024/5/3
* @abstract
*/
@Observed
export class MyMap<K, V> extends Map<K, V> {
public name: string;
constructor(name?: string, args?: [K, V][]) {
super(args);
this.name = name ? name : "My Map";
}
getName() {
return this.name;
}
}
@Observed
export class MySet<T> extends Set<T> {
public name: string;
constructor(name?: string, args?: T[]) {
super(args);
this.name = name ? name : "My Set";
}
getName() {
return this.name;
}
}
let NextID: number = 1;
@Observed
export class ClassA {
public myMap: MyMap<number, string> = new MyMap("myMap", [[0, "a"], [1, "b"], [3, "c"]])
public mySet: MySet<number> = new MySet("Set", [0, 1, 2, 3, 4])
public id: number;
public c: number;
constructor(c: number) {
this.id = NextID++;
this.c = c;
}
}
@Observed
export class ClassB {
public classA: ClassA;
constructor(classA: ClassA) {
this.classA = classA;
}
}
@Component
export struct ObjectLinkCmpt {
// 嵌套对象
@ObjectLink nestedClassA: ClassA;
// 对象数组中的第0项
@ObjectLink classAAtIndex0: ClassA;
// 继承Map类
@ObjectLink myMap: MyMap<number, string>
// 继承Set类
@ObjectLink mySet: MySet<number>
build() {
Column() {
Text(`@ObjectLink nestedClassA ${this.nestedClassA}`)
Text(`@ObjectLink classAAtIndex0 ${this.classAAtIndex0}`)
Text(`@ObjectLink myMap ${this.myMap}`)
Text(`@ObjectLink mySet ${this.mySet}`)
}
}
}
转换后代码
if (!("finalizeConstruction" in ViewPU.prototype)) {
Reflect.set(ViewPU.prototype, "finalizeConstruction", () => { });
}
interface ObjectLinkCmpt_Params {
nestedClassA?: ClassA;
classAAtIndex0?: ClassA;
myMap?: MyMap<number, string>;
mySet?: MySet<number>;
}
/**
*
* ObjectLinkCmpt.ets
* Created by unravel on 2024/5/3
* @abstract
*/
@Observed
export class MyMap<K, V> extends Map<K, V> {
public name: string;
constructor(name?: string, args?: [
K,
V
][]) {
super(args);
this.name = name ? name : "My Map";
}
getName() {
return this.name;
}
}
@Observed
export class MySet<T> extends Set<T> {
public name: string;
constructor(name?: string, args?: T[]) {
super(args);
this.name = name ? name : "My Set";
}
getName() {
return this.name;
}
}
let NextID: number = 1;
@Observed
export class ClassA {
public myMap: MyMap<number, string> = new MyMap("myMap", [[0, "a"], [1, "b"], [3, "c"]]);
public mySet: MySet<number> = new MySet("Set", [0, 1, 2, 3, 4]);
public id: number;
public c: number;
constructor(c: number) {
this.id = NextID++;
this.c = c;
}
}
@Observed
export class ClassB {
public classA: ClassA;
constructor(classA: ClassA) {
this.classA = classA;
}
}
export class ObjectLinkCmpt extends ViewPU {
constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) {
super(parent, __localStorage, elmtId, extraInfo);
if (typeof paramsLambda === "function") {
this.paramsGenerator_ = paramsLambda;
}
this.__nestedClassA = new SynchedPropertyNesedObjectPU(params.nestedClassA, this, "nestedClassA");
this.__classAAtIndex0 = new SynchedPropertyNesedObjectPU(params.classAAtIndex0, this, "classAAtIndex0");
this.__myMap = new SynchedPropertyNesedObjectPU(params.myMap, this, "myMap");
this.__mySet = new SynchedPropertyNesedObjectPU(params.mySet, this, "mySet");
this.setInitiallyProvidedValue(params);
this.finalizeConstruction();
}
setInitiallyProvidedValue(params: ObjectLinkCmpt_Params) {
this.__nestedClassA.set(params.nestedClassA);
this.__classAAtIndex0.set(params.classAAtIndex0);
this.__myMap.set(params.myMap);
this.__mySet.set(params.mySet);
}
updateStateVars(params: ObjectLinkCmpt_Params) {
this.__nestedClassA.set(params.nestedClassA);
this.__classAAtIndex0.set(params.classAAtIndex0);
this.__myMap.set(params.myMap);
this.__mySet.set(params.mySet);
}
purgeVariableDependenciesOnElmtId(rmElmtId) {
this.__nestedClassA.purgeDependencyOnElmtId(rmElmtId);
this.__classAAtIndex0.purgeDependencyOnElmtId(rmElmtId);
this.__myMap.purgeDependencyOnElmtId(rmElmtId);
this.__mySet.purgeDependencyOnElmtId(rmElmtId);
}
aboutToBeDeleted() {
this.__nestedClassA.aboutToBeDeleted();
this.__classAAtIndex0.aboutToBeDeleted();
this.__myMap.aboutToBeDeleted();
this.__mySet.aboutToBeDeleted();
SubscriberManager.Get().delete(this.id__());
this.aboutToBeDeletedInternal();
}
// 嵌套对象
private __nestedClassA: SynchedPropertyNesedObjectPU<ClassA>;
get nestedClassA() {
return this.__nestedClassA.get();
}
// 对象数组中的第0项
private __classAAtIndex0: SynchedPropertyNesedObjectPU<ClassA>;
get classAAtIndex0() {
return this.__classAAtIndex0.get();
}
// 继承Map类
private __myMap: SynchedPropertyNesedObjectPU<MyMap<number, string>
// 继承Set类
>;
get myMap() {
return this.__myMap.get();
}
// 继承Set类
private __mySet: SynchedPropertyNesedObjectPU<MySet<number>>;
get mySet() {
return this.__mySet.get();
}
initialRender() {
this.observeComponentCreation2((elmtId, isInitialRender) => {
Column.create();
}, Column);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create(`@ObjectLink nestedClassA ${this.nestedClassA}`);
}, Text);
Text.pop();
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create(`@ObjectLink classAAtIndex0 ${this.classAAtIndex0}`);
}, Text);
Text.pop();
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create(`@ObjectLink myMap ${this.myMap}`);
}, Text);
Text.pop();
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create(`@ObjectLink mySet ${this.mySet}`);
}, Text);
Text.pop();
Column.pop();
}
rerender() {
this.updateDirtyElements();
}
}
@Observed
转换前后
转换前后并没有什么特别的变化
初始化
初始化的时候也只跟前面的状态装饰器有关。在这里也就是 @State
Observed装饰器
可以看到最终实现的类是ObservedObject。这个类对应的是状态变量的底层实现类
ObservedObject
ObservedObject继承自ExtendableProxy,ExtendableProxy实际上是一个Proxy。ObservedObject 可以参考鸿蒙ACE-状态分析@State。里面有介绍
小结 见框架行为 1.a.
@ObjectLink
转换前后
转换成TS之后,和@State的操作类似,但只有getter
- 将原有属性重写为getter
- 声明一个以双下划线开头的类型为SynchedPropertyNesedObjectPU的私有属性,在getter里访问私有属性的get方法
初始化
通过传入的参数创建了一个SynchedPropertyNesedObjectPU对象
传递
传递的时候似乎也没有什么特别的,通过getter调用状态变量的get。注意这里虽然按照语法传递的是myMap的原始值,但这个原始值其实是一个Proxy,将原有的Map进行了代理
小结 见说明部分 第二条
SynchedPropertyNesedObjectPU
SynchedPropertyNestedObjectPU
这个类继承自ObservedPropertyAbstractPU,ObservedPropertyAbstractPU是状态源对应的类
setValueInternal
最终调用到了ObservedObject.addOwningProperty(this.obsObject_,this)
其中this.obsObject_ 就是我们传入的classA的myMap
ObservedObject
ObservedObject.addOwningProperty
给obj添加了一个SubscribableHandle.SUBSCRIBE属性,属性的值是subscriber。
也就是this.obsObject_[SubscribableHandle.SUBSCRIBE] = this,这样就能通过myMap拿到这个状态变量了
SubscribableHandler
public set(target: Object, property: PropertyKey, newValue: any): boolean
上面给Subscribablehandle.SUBSCRIBE赋值的时候,最终会走SubscribableHandler的set方法
下面我们分别看一下这个类的get和set方法
public get(): C
这一块和@State状态变量一样,可以参考 鸿蒙ACE-状态分析@State
为什么一样?因为@Observed最终转换成了ObservedObject,而ObservedObject恰恰就是@State状态变量的最底层存储实例
public set(newValue: C): void
这一块和@State状态变量一样,可以参考 鸿蒙ACE-状态分析@State
小结 见框架行为 2. 3.
总结
- @Observed装饰一个类之后,这个类最终变成了ObservedObject。ObservedObject也是@State等状态变量的包装类
- 通过this.objectLinkClassA.myMap传递的时候,由于MyMap也被@Observed装饰了,所以最终传递到子组件中的是ObservedObject的引用
- 子组件中@ObjectLink经过编译后被转成只有getter和一个类SynchedPropertyNestedObjectPU,所以就能防止@ObjectLink的变量被赋值
- 在SynchedPropertyNestedObjectPU的setValueInternal中将SynchedPropertyNestedObjectPU这个实例存储到了ObservedObject实例的SubscribableHandle.SUBSCRIBE属性中,进而将SynchedPropertyNestedObjectPU所属组件的id存储到了状态变量的owningProperties_中
- 当状态变量的set方法被调用时,调用状态变量的notifyObjectPropertyHasChanged方法,这个方法中会遍历owningProperties_,然后查找视图所对应的组件,进而调用视图组件的viewPropertyHasChanged方法完成视图的dirty标记。