鸿蒙ACE-V1状态分析@Prop

@Prop装饰器:父子单向同步

developer.huawei.com/consumer/cn...

初始化规则图示

框架行为

要理解@Prop变量值初始化和更新机制,有必要了解父组件和拥有@Prop变量的子组件初始渲染和更新流程。

  1. 初始渲染:

    1. 执行父组件的build()函数将创建子组件的新实例,将数据源传递给子组件;
    2. 初始化子组件@Prop装饰的变量。
  2. 更新:

    1. 子组件@Prop更新时,更新仅停留在当前子组件,不会同步回父组件;
    2. 当父组件的数据源更新时,子组件的@Prop装饰的变量将被来自父组件的数据源重置,所有@Prop装饰的本地的修改将被父组件的更新覆盖。

说明

@Prop装饰的数据更新依赖其所属自定义组件的重新渲染,所以在应用进入后台后,@Prop无法刷新,推荐使用@Link代替。

转换前代码

ts 复制代码
/**
 *
 * PropDemoCmpt.ets
 * Created by unravel on 2024/5/3
 * @abstract
 */
import { StateClass, StateInterface } from './StateDemoPage';
 
@Component
export struct PropChildCmpt {
  // 本地初始化不和父组件同步
  @Prop mineSimpleProp: number = 0;
  // 父组件@State到子组件@Prop简单数据类型同步
  @Prop simpleProp: number = 0;
  // class类型
  @Prop classProp: StateClass = new StateClass('class类型');
  // 从父组件中的@State类对象属性到@Prop简单类型的同步
  @Prop classValueProp: string = '对象属性'
  // interface类型
  @Prop interfaceProp: StateInterface = { value: 'interface类型' };
  // 数组类型
  @Prop arrayProp: StateClass[] = [new StateClass('数组项1'), new StateClass('数组项2')];
  // 父组件@State数组项到子组件@Prop简单数据类型同步
  @Prop array0IndexValue: string = '父组件@State数组项到子组件@Prop简单数据类型同步'
  // 从父组件中的@State数组项到@Prop class类型的同步
  @Prop array0IndexProp: StateClass = new StateClass('数组项0');
  // 日期类型
  @Prop dateProp: Date = new Date('2021-08-08')
  // 装饰Map类型变量
  @Prop mapProp: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]])
  // 装饰Set类型变量
  @Prop setProp: Set<number> = new Set([0, 1, 2, 3, 4])
  // 支持联合类型实例
  @Prop unionProp: number | undefined = 0;
 
  build() {
    Column() {
      Text(`@Prop mineSimpleProp: ${this.mineSimpleProp}`)
      Text(`@Prop simpleProp: ${this.simpleProp}`)
      Text(`@Prop classProp: ${this.classProp.value}`)
      Text(`@Prop classValueProp: ${this.classValueProp}`)
      Text(`@Prop interfaceProp: ${this.interfaceProp.value}`)
      Text(`@Prop arrayProp: ${this.arrayProp.map(item => item.value).join(',')}`)
      Text(`@Prop array0IndexValue: ${this.array0IndexValue}`)
      Text(`@Prop array0IndexProp: ${this.array0IndexProp.value}`)
      Text(`@Prop dateProp: ${this.dateProp}`)
      Text(`@Prop mapProp: ${Array.from(this.mapProp).map((kv: [number, string]) => `${kv[0]}:${kv[1]}`).join(',')}`)
      Text(`@Prop setProp: ${Array.from(this.setProp).join(',')}`)
      Text(`@Prop unionProp: ${this.unionProp}`)
    }
  }
}

转换后代码

ts 复制代码
if (!("finalizeConstruction" in ViewPU.prototype)) {
    Reflect.set(ViewPU.prototype, "finalizeConstruction", () => { });
}
interface PropChildCmpt_Params {
    mineSimpleProp?: number;
    simpleProp?: number;
    classProp?: StateClass;
    classValueProp?: string;
    interfaceProp?: StateInterface;
    arrayProp?: StateClass[];
    array0IndexValue?: string;
    array0IndexProp?: StateClass;
    dateProp?: Date;
    mapProp?: Map<number, string>;
    setProp?: Set<number>;
    unionProp?: number | undefined;
}
import { StateClass } from "@bundle:com.unravel.myapplication/entry/ets/pages/StateDemoPage";
import type { StateInterface } from "@bundle:com.unravel.myapplication/entry/ets/pages/StateDemoPage";
export class PropChildCmpt extends ViewPU {
    constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) {
        super(parent, __localStorage, elmtId, extraInfo);
        if (typeof paramsLambda === "function") {
            this.paramsGenerator_ = paramsLambda;
        }
        this.__mineSimpleProp = new SynchedPropertySimpleOneWayPU(params.mineSimpleProp, this, "mineSimpleProp");
        this.__simpleProp = new SynchedPropertySimpleOneWayPU(params.simpleProp, this, "simpleProp");
        this.__classProp = new SynchedPropertyObjectOneWayPU(params.classProp, this, "classProp");
        this.__classValueProp = new SynchedPropertySimpleOneWayPU(params.classValueProp, this, "classValueProp");
        this.__interfaceProp = new SynchedPropertyObjectOneWayPU(params.interfaceProp, this, "interfaceProp");
        this.__arrayProp = new SynchedPropertyObjectOneWayPU(params.arrayProp, this, "arrayProp");
        this.__array0IndexValue = new SynchedPropertySimpleOneWayPU(params.array0IndexValue, this, "array0IndexValue");
        this.__array0IndexProp = new SynchedPropertyObjectOneWayPU(params.array0IndexProp, this, "array0IndexProp");
        this.__dateProp = new SynchedPropertyObjectOneWayPU(params.dateProp, this, "dateProp");
        this.__mapProp = new SynchedPropertyObjectOneWayPU(params.mapProp, this, "mapProp");
        this.__setProp = new SynchedPropertyObjectOneWayPU(params.setProp, this, "setProp");
        this.__unionProp = new SynchedPropertyObjectOneWayPU(params.unionProp, this, "unionProp");
        this.setInitiallyProvidedValue(params);
        this.finalizeConstruction();
    }
    setInitiallyProvidedValue(params: PropChildCmpt_Params) {
        if (params.mineSimpleProp === undefined) {
            this.__mineSimpleProp.set(0);
        }
        if (params.simpleProp === undefined) {
            this.__simpleProp.set(0);
        }
        if (params.classProp === undefined) {
            this.__classProp.set(new StateClass('class类型'));
        }
        if (params.classValueProp === undefined) {
            this.__classValueProp.set('对象属性'
            // interface类型
            );
        }
        if (params.interfaceProp === undefined) {
            this.__interfaceProp.set({ value: 'interface类型' });
        }
        if (params.arrayProp === undefined) {
            this.__arrayProp.set([new StateClass('数组项1'), new StateClass('数组项2')]);
        }
        if (params.array0IndexValue === undefined) {
            this.__array0IndexValue.set('父组件@State数组项到子组件@Prop简单数据类型同步'
            // 从父组件中的@State数组项到@Prop class类型的同步
            );
        }
        if (params.array0IndexProp === undefined) {
            this.__array0IndexProp.set(new StateClass('数组项0'));
        }
        if (params.dateProp === undefined) {
            this.__dateProp.set(new Date('2021-08-08')
            // 装饰Map类型变量
            );
        }
        if (params.mapProp === undefined) {
            this.__mapProp.set(new Map([[0, "a"], [1, "b"], [3, "c"]])
            // 装饰Set类型变量
            );
        }
        if (params.setProp === undefined) {
            this.__setProp.set(new Set([0, 1, 2, 3, 4])
            // 支持联合类型实例
            );
        }
        if (params.unionProp === undefined) {
            this.__unionProp.set(0);
        }
    }
    updateStateVars(params: PropChildCmpt_Params) {
        this.__mineSimpleProp.reset(params.mineSimpleProp);
        this.__simpleProp.reset(params.simpleProp);
        this.__classProp.reset(params.classProp);
        this.__classValueProp.reset(params.classValueProp);
        this.__interfaceProp.reset(params.interfaceProp);
        this.__arrayProp.reset(params.arrayProp);
        this.__array0IndexValue.reset(params.array0IndexValue);
        this.__array0IndexProp.reset(params.array0IndexProp);
        this.__dateProp.reset(params.dateProp);
        this.__mapProp.reset(params.mapProp);
        this.__setProp.reset(params.setProp);
        this.__unionProp.reset(params.unionProp);
    }
    purgeVariableDependenciesOnElmtId(rmElmtId) {
        this.__mineSimpleProp.purgeDependencyOnElmtId(rmElmtId);
        this.__simpleProp.purgeDependencyOnElmtId(rmElmtId);
        this.__classProp.purgeDependencyOnElmtId(rmElmtId);
        this.__classValueProp.purgeDependencyOnElmtId(rmElmtId);
        this.__interfaceProp.purgeDependencyOnElmtId(rmElmtId);
        this.__arrayProp.purgeDependencyOnElmtId(rmElmtId);
        this.__array0IndexValue.purgeDependencyOnElmtId(rmElmtId);
        this.__array0IndexProp.purgeDependencyOnElmtId(rmElmtId);
        this.__dateProp.purgeDependencyOnElmtId(rmElmtId);
        this.__mapProp.purgeDependencyOnElmtId(rmElmtId);
        this.__setProp.purgeDependencyOnElmtId(rmElmtId);
        this.__unionProp.purgeDependencyOnElmtId(rmElmtId);
    }
    aboutToBeDeleted() {
        this.__mineSimpleProp.aboutToBeDeleted();
        this.__simpleProp.aboutToBeDeleted();
        this.__classProp.aboutToBeDeleted();
        this.__classValueProp.aboutToBeDeleted();
        this.__interfaceProp.aboutToBeDeleted();
        this.__arrayProp.aboutToBeDeleted();
        this.__array0IndexValue.aboutToBeDeleted();
        this.__array0IndexProp.aboutToBeDeleted();
        this.__dateProp.aboutToBeDeleted();
        this.__mapProp.aboutToBeDeleted();
        this.__setProp.aboutToBeDeleted();
        this.__unionProp.aboutToBeDeleted();
        SubscriberManager.Get().delete(this.id__());
        this.aboutToBeDeletedInternal();
    }
    // 本地初始化不和父组件同步
    private __mineSimpleProp: SynchedPropertySimpleOneWayPU<number>;
    get mineSimpleProp() {
        return this.__mineSimpleProp.get();
    }
    set mineSimpleProp(newValue: number) {
        this.__mineSimpleProp.set(newValue);
    }
    // 父组件@State到子组件@Prop简单数据类型同步
    private __simpleProp: SynchedPropertySimpleOneWayPU<number>;
    get simpleProp() {
        return this.__simpleProp.get();
    }
    set simpleProp(newValue: number) {
        this.__simpleProp.set(newValue);
    }
    // class类型
    private __classProp: SynchedPropertySimpleOneWayPU<StateClass>;
    get classProp() {
        return this.__classProp.get();
    }
    set classProp(newValue: StateClass) {
        this.__classProp.set(newValue);
    }
    // 从父组件中的@State类对象属性到@Prop简单类型的同步
    private __classValueProp: SynchedPropertySimpleOneWayPU<string>;
    get classValueProp() {
        return this.__classValueProp.get();
    }
    set classValueProp(newValue: string) {
        this.__classValueProp.set(newValue);
    }
    // interface类型
    private __interfaceProp: SynchedPropertySimpleOneWayPU<StateInterface>;
    get interfaceProp() {
        return this.__interfaceProp.get();
    }
    set interfaceProp(newValue: StateInterface) {
        this.__interfaceProp.set(newValue);
    }
    // 数组类型
    private __arrayProp: SynchedPropertySimpleOneWayPU<StateClass[]>;
    get arrayProp() {
        return this.__arrayProp.get();
    }
    set arrayProp(newValue: StateClass[]) {
        this.__arrayProp.set(newValue);
    }
    // 父组件@State数组项到子组件@Prop简单数据类型同步
    private __array0IndexValue: SynchedPropertySimpleOneWayPU<string>;
    get array0IndexValue() {
        return this.__array0IndexValue.get();
    }
    set array0IndexValue(newValue: string) {
        this.__array0IndexValue.set(newValue);
    }
    // 从父组件中的@State数组项到@Prop class类型的同步
    private __array0IndexProp: SynchedPropertySimpleOneWayPU<StateClass>;
    get array0IndexProp() {
        return this.__array0IndexProp.get();
    }
    set array0IndexProp(newValue: StateClass) {
        this.__array0IndexProp.set(newValue);
    }
    // 日期类型
    private __dateProp: SynchedPropertySimpleOneWayPU<Date>;
    get dateProp() {
        return this.__dateProp.get();
    }
    set dateProp(newValue: Date) {
        this.__dateProp.set(newValue);
    }
    // 装饰Map类型变量
    private __mapProp: SynchedPropertySimpleOneWayPU<Map<number, string>>;
    get mapProp() {
        return this.__mapProp.get();
    }
    set mapProp(newValue: Map<number, string>) {
        this.__mapProp.set(newValue);
    }
    // 装饰Set类型变量
    private __setProp: SynchedPropertySimpleOneWayPU<Set<number>>;
    get setProp() {
        return this.__setProp.get();
    }
    set setProp(newValue: Set<number>) {
        this.__setProp.set(newValue);
    }
    // 支持联合类型实例
    private __unionProp: SynchedPropertySimpleOneWayPU<number | undefined>;
    get unionProp() {
        return this.__unionProp.get();
    }
    set unionProp(newValue: number | undefined) {
        this.__unionProp.set(newValue);
    }
    initialRender() {
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Column.create();
        }, Column);
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Text.create(`@Prop mineSimpleProp: ${this.mineSimpleProp}`);
        }, Text);
        Text.pop();
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Text.create(`@Prop simpleProp: ${this.simpleProp}`);
        }, Text);
        Text.pop();
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Text.create(`@Prop classProp: ${this.classProp.value}`);
        }, Text);
        Text.pop();
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Text.create(`@Prop classValueProp: ${this.classValueProp}`);
        }, Text);
        Text.pop();
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Text.create(`@Prop interfaceProp: ${this.interfaceProp.value}`);
        }, Text);
        Text.pop();
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Text.create(`@Prop arrayProp: ${this.arrayProp.map(item => item.value).join(',')}`);
        }, Text);
        Text.pop();
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Text.create(`@Prop array0IndexValue: ${this.array0IndexValue}`);
        }, Text);
        Text.pop();
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Text.create(`@Prop array0IndexProp: ${this.array0IndexProp.value}`);
        }, Text);
        Text.pop();
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Text.create(`@Prop dateProp: ${this.dateProp}`);
        }, Text);
        Text.pop();
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Text.create(`@Prop mapProp: ${Array.from(ObservedObject.GetRawObject(this.mapProp)).map((kv: [
                number,
                string
            ]) => `${kv[0]}:${kv[1]}`).join(',')}`);
        }, Text);
        Text.pop();
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Text.create(`@Prop setProp: ${Array.from(ObservedObject.GetRawObject(this.setProp)).join(',')}`);
        }, Text);
        Text.pop();
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Text.create(`@Prop unionProp: ${this.unionProp}`);
        }, Text);
        Text.pop();
        Column.pop();
    }
    rerender() {
        this.updateDirtyElements();
    }
}

@Prop

转换前后

转换成TS之后,和@State的操作类似。

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

初始化

  1. @Prop状态变量转换成TS代码之后对应两个不同的类
  2. SynchedPropertySimpleOneWayPU 由简单类型转换而来,不管这个简单类型是否是由父组件中状态变量的属性传递而来,还是基本类型传递而来。只要声明的时候是简单类型,就会生成SynchedPropertySimpleOneWayPU
  3. SynchedPropertyObjectOneWayPU 由非简单类型转换而来

传递

命名参数传递过来的是状态的原始值。这里其实是调用的simpleState的getter,simpleState的getter返回的是状态对象ObservedPropertySimplePU里wrapperValue包装的原始值

simpleState的getter内调用__simpleState.get()获取__simpleState包装的原始值

小结:见 框架行为 1.

SynchedPropertyOneWayPU

SynchedPropertySimpleOneWayPU和SynchedPropertySimpleOneWayPU 是SynchedPropertyOneWayPU的子类

至于怎么区分的,我们可以在gitee.com/openharmony...里找到代码

判断逻辑和@State很类似,可以参考 鸿蒙ACE-状态分析@State ObservedPropertySimplePU、ObservedPropertyObjectPU 部分

结论:isBasicType里面判断,如果是string、boolean、number、enum、bigin以及他们的包装类String、Boolean、Number等都是基础类型

关联UI

ObservedPropertyAbstractPU

SynchedPropertyOneWayPU继承自ObservedPropertyAbstractPU,在constructor的时候将视图和属性传入了父类ObservedPropertyAbstractPU

之后的逻辑就和@State关联UI很相似,可以参考 鸿蒙ACE-状态分析@State ObservedPropertyPU 怎么关联UI? 部分

结论:状态变量创建的时候会将视图this传进去,在状态变量对象内部通过owningView持有了视图实例

关联使用状态变量的UI

get方法里调用了recordPropertyDependentUpdate, 这部分可以查看 鸿蒙ACE-状态分析@State

更新UI

set

set方法和@State的set方法很相似,参考鸿蒙ACE-状态分析@State ObservedPropertyPU 怎么更新UI? 部分

结论:

我们写的ArkTS代码经过转换后生成TS代码,创建组件的代码被包装进一个箭头函数传入observeComponentCreation2,然后内部又对传入的这个箭头函数做了一层包装,并且存储下来

之后状态变量发生变化的时候,就拿到它所持有的view,并通过elemId拿到存储下来的更新函数并且调用

小结:框架行为 2.a

  1. @Prop装饰的变量最终也会生成一个 ObservedPropertyAbstractPU 实例,ObservedPropertyAbstractPU这个类是所有状态数据源的父类。所以@Prop作为数据源和@State的流程非常类似

@Prop 对数据深拷贝

SynchedPropertyOneWayPU

constructor(source: ObservedPropertyAbstract | C, owningChildView: IPropertySubscriber, thisPropertyName: PropertyInfo)

在SynchedPropertyOneWayPU的constructor中我们可以看到,对数据源拷贝使用了resetLocalValue

private resetLocalValue(newObservedObjectValue: C, needCopyObject: boolean): boolean
private copyObject(value: C, propName: string): C

可以看到,在API9以后都是执行的深拷贝

小结:框架行为 1.

数据源更改时,@Prop状态变量怎么更新?

概述

源码里面针对初始化和更新场景有一段解释

我们让GPT翻译一下

初始化场景:

  1. 没有本地初始化,提供了源(它的ObservedObject值):

    • 将ObservedObject包装成一个ObservedPropertyObjectPU
    • 将ObservedObject深拷贝到localCopyObservedObject_
  2. 本地初始化,没有提供源:

    • 应用程序的转译代码调用set。
    • 保留source_为undefined。
    • 不需要深拷贝,但是提供的本地初始化可能需要包装在一个ObservedObject中以设置到localCopyObservedObject_。
  3. 本地初始化,提供了源(它的ObservedObject值):

    • 当前应用程序的转译代码不是可选的。
    • 在构造函数中设置源,如情况1。
    • 调用set()来设置源值,但这不会进行深拷贝。

更新场景:

  1. 分配一个新的Object值:this.aProp = new ClassA()

    • rhs可以是ObservedObject,因为@Observed装饰或现在。
    • notifyPropertyHasChangedPU。
  2. 本地ObservedObject成员属性更改

    • 调用objectPropertyHasChangedPU,eventSource是存储在localCopyObservedObject_中的ObservedObject。
    • 不需要复制,notifyPropertyHasChangedPU。
  3. 从父级触发的自定义组件的重新渲染

    • 调用reset()(由转译器生成的代码),设置source_的值,如果这导致了更改,将调用syncPeerHasChanged。
    • syncPeerHasChanged需要将ObservedObject从源深拷贝到localCopyObservedObject_。
    • notifyPropertyHasChangedPU。
  4. source_ ObservedObject成员属性更改

    • 调用objectPropertyHasChangedPU,eventSource是存储在source_.getUnmonitored中的ObservedObject。
    • notifyPropertyHasChangedPU。

初始化

初始化的时候使用命名方式传递,注意此时的this.simpleState是一个getter,getter获取的是原始类型的值。比如simpleState是一个number,此时传递给@Prop的还是一个number,只不过@Prop内部又对number进行了包装

更新

updateStateVarsOfChildByElmtId

父组件更新时会调用updateStateVarsOfChildByElmtId

PUV2ViewBase
public updateStateVarsOfChildByElmtId(elmtId, params: Object): void

updateStateVarsOfChildByElmtId内部根据elmtId拿到所需要更新的子组件,然后调用子组件的updateStateVars,并将上一步传入的参数传到updateStateVars中

public abstract updateStateVars(params: Object);

子组件的updateStateVars方法内,各个@Prop状态变量根据传入的参数,调用对应的reset方法

SynchedPropertyOneWayPU
public reset(sourceChangedValue: C): void

reset方法内部,调用了source_的set方法

source_来源

source_是@Prop状态变量初始化时创建的一个类ObservedPropertyObjectPU

ObservedPropertyObjectPU这个类是不是很熟悉,它就是@State状态变量对应的类

name调用set方法就和@State状态变量的set方法很相似了

之后就可以看 鸿蒙ACE-状态分析@State notifyPropertyHasChangedPU、notifyTrackedObjectPropertyHasChanged 部分

ObservedPropertyAbstractPU
protected notifyPropertyHasChangedPU()

set内部会调用 notifyPropertyHasChangedPU

SynchedPropertyOneWayPU
public syncPeerHasChanged(eventSource: ObservedPropertyAbstractPU): void
syncPeerHasChanged 内部又会调用restLocalValue进行深度拷贝并调用notifyPropertyHasChangedPU驱动UI更新

后面就和@State一样了,可以参考 鸿蒙ACE-状态分析@State notifyPropertyHasChangedPU、notifyTrackedObjectPropertyHasChanged 部分

小结:框架行为 2.b.

总结

  1. @Prop状态变量和@State类似,在编译后,原有变量被重写成getter和setter,同时声明一个私有的变量用于存储原有变量
  2. @Prop初始化时,在constructor内会调用resetLocalValue对原有数据进行深拷贝
  3. @Prop变化时和@State变化时是一样的逻辑,一系列逻辑处理完成后会调用notifyPropertyHasChangedPU驱动UI刷新
  4. 数据源(@State)变化时,会调用@Prop里面的reset,reset内会再次进行深拷贝,最后会调用notifyPropertyHasChangedPU驱动UI刷新
  5. @Prop内实际的存储是source_,source_是 ObservedPropertyObjectPU类的一个实例,这个实例是@State的状态对应的类。所以@Prop的观察也只能观察一层

图示

参考资料

  1. gitee.com/openharmony...
  2. 声明式范式的语法编译转换,语法验证 https://gitee.com/openharmony/developtools_ace_ets2bundle
相关推荐
HarderCoder3 小时前
鸿蒙ACE-状态源码分析系列
harmonyos
结城明日奈是我老婆4 小时前
harmonyOS ArkTS最新跳转Navigation
华为·harmonyos
star@星空4 小时前
harmonyos面试题
华为·harmonyos
积跬步DEV11 小时前
鸿蒙 WebView 如何 Debug
harmonyos
一码平川SHI18 小时前
鸿蒙_异步详解
华为·harmonyos
郝晨妤18 小时前
鸿蒙【项目打包】- .hap 和 .app;(测试如何安装发的hap包)(应用上架流程)
华为od·华为·harmonyos·鸿蒙
若兰幽竹18 小时前
【鸿蒙HarmonyOS NEXT】用户首选项Preference存储数据
harmonyos
SuperHeroWu718 小时前
【HarmonyOS】深入理解@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化
华为·harmonyos·状态管理·observed·objectlink·嵌套对象