首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

鸿蒙ACE-V1状态分析@Track

  • 24-12-10 09:04
  • 3143
  • 13836
juejin.cn

@Track装饰器:class对象属性级更新

developer.huawei.com/consumer/cn…

@Track是class对象的属性装饰器。当一个class对象是状态变量时,@Track装饰的属性发生变化,只会触发该属性关联的UI更新;而未被标记的属性不能在UI中使用

观察变化和行为表现

当一个class对象是状态变量时,@Track装饰的属性发生变化,该属性关联的UI触发更新。

说明:当class对象中没有一个属性被标记@Track,行为与原先保持不变。@Track没有深度观测的功能。

代码转换前

/**
 *
 * TrackChildCmpt.ets
 * Created by unravel on 2024/5/3
 * @abstract
 */
class LogTrack {
  @Track str1: string;
  @Track str2: string;

  constructor(str1: string) {
    this.str1 = str1;
    this.str2 = 'World';
  }
}

class LogNotTrack {
  str1: string;
  str2: string;

  constructor(str1: string) {
    this.str1 = str1;
    this.str2 = '世界';
  }
}

@Component
export struct TrackChildCmpt {
  @State logTrack: LogTrack = new LogTrack('Hello');
  @State logNotTrack: LogNotTrack = new LogNotTrack('你好');

  isRender(index: number) {
    console.log(`Text ${index} is rendered`);
    return 50;
  }

  build() {
    Row() {
      Column() {
        Text(this.logTrack.str1)// UINode1
          .fontSize(this.isRender(1))
          .fontWeight(FontWeight.Bold)
        Text(this.logTrack.str2)// UINode2
          .fontSize(this.isRender(2))
          .fontWeight(FontWeight.Bold)
        Button('change logTrack.str1')
          .onClick(() => {
            this.logTrack.str1 = 'Bye';
          })
        Text(this.logNotTrack.str1)// UINode3
          .fontSize(this.isRender(3))
          .fontWeight(FontWeight.Bold)
        Text(this.logNotTrack.str2)// UINode4
          .fontSize(this.isRender(4))
          .fontWeight(FontWeight.Bold)
        Button('change logNotTrack.str1')
          .onClick(() => {
            this.logNotTrack.str1 = '再见';
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

代码转换后

if (!("finalizeConstruction" in ViewPU.prototype)) {
    Reflect.set(ViewPU.prototype, "finalizeConstruction", () => { });
}
interface TrackChildCmpt_Params {
    logTrack?: LogTrack;
    logNotTrack?: LogNotTrack;
}
/**
 *
 * TrackChildCmpt.ets
 * Created by unravel on 2024/5/3
 * @abstract
 */
class LogTrack {
    @Track
    str1: string;
    @Track
    str2: string;
    constructor(str1: string) {
        this.str1 = str1;
        this.str2 = 'World';
    }
}
class LogNotTrack {
    str1: string;
    str2: string;
    constructor(str1: string) {
        this.str1 = str1;
        this.str2 = '世界';
    }
}
export class TrackChildCmpt extends ViewPU {
    constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) {
        super(parent, __localStorage, elmtId, extraInfo);
        if (typeof paramsLambda === "function") {
            this.paramsGenerator_ = paramsLambda;
        }
        this.__logTrack = new ObservedPropertyObjectPU(new LogTrack('Hello'), this, "logTrack");
        this.__logNotTrack = new ObservedPropertyObjectPU(new LogNotTrack('你好'), this, "logNotTrack");
        this.setInitiallyProvidedValue(params);
        this.finalizeConstruction();
    }
    setInitiallyProvidedValue(params: TrackChildCmpt_Params) {
        if (params.logTrack !== undefined) {
            this.logTrack = params.logTrack;
        }
        if (params.logNotTrack !== undefined) {
            this.logNotTrack = params.logNotTrack;
        }
    }
    updateStateVars(params: TrackChildCmpt_Params) {
    }
    purgeVariableDependenciesOnElmtId(rmElmtId) {
        this.__logTrack.purgeDependencyOnElmtId(rmElmtId);
        this.__logNotTrack.purgeDependencyOnElmtId(rmElmtId);
    }
    aboutToBeDeleted() {
        this.__logTrack.aboutToBeDeleted();
        this.__logNotTrack.aboutToBeDeleted();
        SubscriberManager.Get().delete(this.id__());
        this.aboutToBeDeletedInternal();
    }
    private __logTrack: ObservedPropertyObjectPU<LogTrack>;
    get logTrack() {
        return this.__logTrack.get();
    }
    set logTrack(newValue: LogTrack) {
        this.__logTrack.set(newValue);
    }
    private __logNotTrack: ObservedPropertyObjectPU<LogNotTrack>;
    get logNotTrack() {
        return this.__logNotTrack.get();
    }
    set logNotTrack(newValue: LogNotTrack) {
        this.__logNotTrack.set(newValue);
    }
    isRender(index: number) {
        console.log(`Text ${index} is rendered`);
        return 50;
    }
    initialRender() {
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Row.create();
            Row.height('100%');
        }, Row);
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Column.create();
            Column.width('100%');
        }, Column);
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Text.create(this.logTrack.str1);
            Text.fontSize(this.isRender(1));
            Text.fontWeight(FontWeight.Bold);
        }, Text);
        Text.pop();
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Text.create(this.logTrack.str2);
            Text.fontSize(this.isRender(2));
            Text.fontWeight(FontWeight.Bold);
        }, Text);
        Text.pop();
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Button.createWithLabel('change logTrack.str1');
            Button.onClick(() => {
                this.logTrack.str1 = 'Bye';
            });
        }, Button);
        Button.pop();
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Text.create(this.logNotTrack.str1);
            Text.fontSize(this.isRender(3));
            Text.fontWeight(FontWeight.Bold);
        }, Text);
        Text.pop();
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Text.create(this.logNotTrack.str2);
            Text.fontSize(this.isRender(4));
            Text.fontWeight(FontWeight.Bold);
        }, Text);
        Text.pop();
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Button.createWithLabel('change logNotTrack.str1');
            Button.onClick(() => {
                this.logNotTrack.str1 = '再见';
            });
        }, Button);
        Button.pop();
        Column.pop();
        Row.pop();
    }
    rerender() {
        this.updateDirtyElements();
    }
}

@Track

转换前后

@Track转换成TS之后,@Track并没有被转换成其他的代码

image.png

状态变量

可以看到状态变量的修改是@State的底层转换逻辑。 @Track属性没有特殊的逻辑

image.png

转换前后的传参

使用和传参的地方,转换前后也没有区别。

image.png

Track装饰器

我们知道@Track是一个属性装饰器,在ace源码里面搜索一下这个装饰器的实现

Track

function Track(target: Object, property: string)

Track是一个属性装饰器,它给属性所属的对象实例添加了两个boolean类型的属性,并且值都为true。

我们看下这两个属性在哪里被用到了

image.png

TrackedObject

有两个方法中使用了 TrackedObject.___IS_TRACKED_OPTIMISED。我们分别看一下

public static isCompatibilityMode(obj: Object): boolean

用于判断是否是兼容模式,这包括基本类型、未使用@Track装饰属性的class类型、Object类型

  1. !obj 用于处理undefined和null。 这是因为API11之后undefined和null也可以作为状态变量了
  2. (typeof obj !== 'object') 用于判断普通基本类型以及他们的包装类型(boolean,number,string,Boolean,Number,String)
  3. !Reflect.has(obj, TrackedObject.___IS_TRACKED_OPTIMISED   用于判断未被@Track装饰的class和Object(Object包括所有字面量、new 创建的所有对象)

image.png

public static needsPropertyReadCb(obj: Object): boolean

用于判断是使用了@Track装饰了属性的对象

image.png

TrackedObject.___TRACKED_PREFIX

从Track装饰器的实现我们知道,使用Track之后给对象添加了一个${TrackedObject.___TRACKED_PREFIX}${property}的boolean属性

我们搜一下TrackedObject.___TRACKED_PREFIX的使用。有一处调用,位于TrackedObject的 notifyObjectValueAssignment 方法

public static notifyObjectValueAssignment(obj1: Object, obj2: Object, notifyPropertyChanged: () => void, // notify as assignment (none-optimised) notifyTrackedPropertyChange: (propName) => void, obSelf: ObservedPropertyAbstractPU): boolean

这个方法是在状态变量的set方法内调用的,可以回忆一下 鸿蒙ACE-状态分析@State 的set方法

image.png

notifyPropertyChanged

用于通知普通属性变化,对应于ObservedPropertyPU、SynchedPropertyNestedObjectPU、SynchedPropertyOneWayPU、SynchedPropertyTwoWayPU等的 notifyPropertyHasChangedPU

notifyTrackedPropertyChange

用于通知Track属性变化,对应于ObservedPropertyPU、SynchedPropertyNestedObjectPU、SynchedPropertyOneWayPU、SynchedPropertyTwoWayPU等的 notifyTrackedObjectPropertyHasChanged

关于这两个函数的实现可以查看 鸿蒙ACE-状态分析@State  notifyPropertyHasChangedPU、notifyTrackedObjectPropertyHasChanged 部分

ObservedPropertyAbstractPU

我们这次仔细看一下notifyTrackedObjectPropertyHasChanged的实现

protected notifyTrackedObjectPropertyHasChanged(changedPropertyName : string) : void

可以看到内部调用viewPropertyHasChanged和syncPeerTrackedPropertyHasChanged的时候,都是获取依赖对应属性的组件id。

我们看下怎么获取的@Track属性关联的那些组件

image.png

PropertyDependencies

public getTrackedObjectPropertyDependencies(changedObjectProperty: string, debugInfo: string): Set

依赖某个属性的所有组件存在于一个Map中。key是属性的名称,value是依赖这个属性的组件的id组成的集合

我们看到依赖的组件id是通过 addTrackedObjectPropertyDependency 添加的。看下谁调用了这个方法

类似的逻辑可以参考 鸿蒙ACE-状态分析@State

image.png

protected recordTrackObjectPropertyDependencyForElmtId(renderingElmtId : number, readTrackedPropertyName : string) : void

那么调用这个方法的地方又有哪些呢?

image.png

ObservedPropertyPU

protected onOptimisedObjectPropertyRead(readObservedObject: T, readPropertyName: string, isTracked: boolean) : void

可以看到这个方法是一个回调,方法名很容易理解,被优化的属性读取时的回调,这里被优化的属性就是@Track装饰的属性。

再看一下 onOptimisedObjectPropertyRead 回调什么时候被调用

image.png

public get(): T

image.png

ok,我们再看一下shouldInstallTrackedObjectReadCb这个标识什么时候为true

private setValueInternal(newValue: T): boolean

我们发现在setValueInternal方法里面,会根据TrackedObject.needsPropertyReadCb的返回值将shouldInstallTrackedObjectReadCb置为true或false

TrackedObject.needsPropertyReadCb这个方法我们在上面看到过了,如果有这个对象里面有@Track属性,就会置为true

同样的我们这里还需要提一嘴,在ObservedPropertyPU的constructor和set方法里都会调用setValueInternal。所以组件一创建的时候shouldInstallTrackedObjectReadCb就是true了

image.png

总结

我们总结一下Track装饰器生效的过程

  1. @Track装饰属性,编译完成后会给属性所在实例对象添加两个额外属性

    1. 一个是boolean属性TrackedObject.___IS_TRACKED_OPTIMISED,标志这个对象被Track装饰过
    2. 一个是boolean属性TrackedObject.___TRACKED_PREFIX+属性的本来名称。可以通过它来区分该属性是普通属性还是Track属性
  2. @Track装饰的类是一个状态变量时

    1. 状态变量底层转换成ObservedPropertyObjectPU,在这个对象初始化时会将shouldInstallTrackedObjectReadCb置为true
    2. 之后我们在组件中通过get获取这个属性时,会将组件id保存在一个Map中,这个Map以属性名为key,依赖这个属性的所有组件id组成的Set为value
    3. 在我们通过set方法修改属性时,在Map中找到依赖这个属性的所有的组件id,然后将这些组件标记为dirty。然后再下次Vsync过来时,这些组件就会被更新

参考资料

  1. wangdoc.com/es6/proxy
  2. gitee.com/openharmony…
  3. 声明式范式的语法编译转换,语法验证 https://gitee.com/openharmony/developtools_ace_ets2bundle
注:本文转载自juejin.cn的HarderCoder的文章"https://juejin.cn/post/7418109561108561930"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

141
iOS
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top