鸿蒙ACE-V1状态分析@Observed、@ObjectLink

122 阅读5分钟

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

developer.huawei.com/consumer/cn…

初始化规则图示

说明

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

  • @Prop装饰的变量和数据源的关系是是单向同步,@Prop装饰的变量在本地拷贝了数据源,所以它允许本地更改,如果父组件中的数据源有更新,@Prop装饰的变量本地的修改将被覆盖;
  • @ObjectLink装饰的变量和数据源的关系是双向同步,@ObjectLink装饰的变量相当于指向数据源的指针禁止对@ObjectLink装饰的变量赋值,如果一旦发生@ObjectLink装饰的变量的赋值,则同步链将被打断。因为@ObjectLink装饰的变量通过数据源(Object)引用来初始化。对于实现双向数据同步的@ObjectLink,赋值相当于更新父组件中的数组项或者class的属性,TypeScript/JavaScript不能实现,会发生运行时报错。

框架行为

  1. 初始渲染:

    1. @Observed装饰的class的实例会被不透明的代理对象包装,代理了class上的属性的setter和getter方法
    2. 子组件中@ObjectLink装饰的class实例,接收被@Observed装饰的class的实例,@ObjectLink的包装类会将自己注册给@Observed class。
  2. 属性更新:当@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

转换前后

转换前后并没有什么特别的变化

image.png

初始化

初始化的时候也只跟前面的状态装饰器有关。在这里也就是 @State

image.png

Observed装饰器

可以看到最终实现的类是ObservedObject。这个类对应的是状态变量的底层实现类

image.png

ObservedObject

ObservedObject继承自ExtendableProxy,ExtendableProxy实际上是一个Proxy。ObservedObject 可以参考鸿蒙ACE-状态分析@State。里面有介绍

小结 见框架行为 1.a.

@ObjectLink 

转换前后

转换成TS之后,和@State的操作类似,但只有getter

  1. 将原有属性重写为getter
  2. 声明一个以双下划线开头的类型为SynchedPropertyNesedObjectPU的私有属性,在getter里访问私有属性的get方法

image.png

初始化

通过传入的参数创建了一个SynchedPropertyNesedObjectPU对象

image.png

传递

传递的时候似乎也没有什么特别的,通过getter调用状态变量的get。注意这里虽然按照语法传递的是myMap的原始值,但这个原始值其实是一个Proxy,将原有的Map进行了代理

image.png

image.png

小结 见说明部分 第二条

SynchedPropertyNesedObjectPU

image.png

SynchedPropertyNestedObjectPU

这个类继承自ObservedPropertyAbstractPU,ObservedPropertyAbstractPU是状态源对应的类

image.png

setValueInternal

最终调用到了ObservedObject.addOwningProperty(this.obsObject_,this)

其中this.obsObject_ 就是我们传入的classA的myMap

image.png

ObservedObject

ObservedObject.addOwningProperty

给obj添加了一个SubscribableHandle.SUBSCRIBE属性,属性的值是subscriber。

也就是this.obsObject_[SubscribableHandle.SUBSCRIBE] = this,这样就能通过myMap拿到这个状态变量了

image.png

SubscribableHandler

public set(target: Object, property: PropertyKey, newValue: any): boolean

上面给Subscribablehandle.SUBSCRIBE赋值的时候,最终会走SubscribableHandler的set方法

image.png

下面我们分别看一下这个类的get和set方法

public get(): C

这一块和@State状态变量一样,可以参考 鸿蒙ACE-状态分析@State

为什么一样?因为@Observed最终转换成了ObservedObject,而ObservedObject恰恰就是@State状态变量的最底层存储实例

image.png

public set(newValue: C): void

这一块和@State状态变量一样,可以参考 鸿蒙ACE-状态分析@State

image.png

小结 见框架行为 2.  3.

总结

  1. @Observed装饰一个类之后,这个类最终变成了ObservedObject。ObservedObject也是@State等状态变量的包装类
  2. 通过this.objectLinkClassA.myMap传递的时候,由于MyMap也被@Observed装饰了,所以最终传递到子组件中的是ObservedObject的引用
  3. 子组件中@ObjectLink经过编译后被转成只有getter和一个类SynchedPropertyNestedObjectPU,所以就能防止@ObjectLink的变量被赋值
  4. 在SynchedPropertyNestedObjectPU的setValueInternal中将SynchedPropertyNestedObjectPU这个实例存储到了ObservedObject实例的SubscribableHandle.SUBSCRIBE属性中,进而将SynchedPropertyNestedObjectPU所属组件的id存储到了状态变量的owningProperties_中
  5. 当状态变量的set方法被调用时,调用状态变量的notifyObjectPropertyHasChanged方法,这个方法中会遍历owningProperties_,然后查找视图所对应的组件,进而调用视图组件的viewPropertyHasChanged方法完成视图的dirty标记。