@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并没有被转换成其他的代码
状态变量
可以看到状态变量的修改是@State的底层转换逻辑。 @Track属性没有特殊的逻辑
转换前后的传参
使用和传参的地方,转换前后也没有区别。
Track装饰器
我们知道@Track是一个属性装饰器,在ace源码里面搜索一下这个装饰器的实现
Track
function Track(target: Object, property: string)
Track是一个属性装饰器,它给属性所属的对象实例添加了两个boolean类型的属性,并且值都为true。
我们看下这两个属性在哪里被用到了
TrackedObject
有两个方法中使用了 TrackedObject.___IS_TRACKED_OPTIMISED。我们分别看一下
public static isCompatibilityMode(obj: Object): boolean
用于判断是否是兼容模式,这包括基本类型、未使用@Track装饰属性的class类型、Object类型
- !obj 用于处理undefined和null。 这是因为API11之后undefined和null也可以作为状态变量了
- (typeof obj !== 'object') 用于判断普通基本类型以及他们的包装类型(boolean,number,string,Boolean,Number,String)
- !Reflect.has(obj, TrackedObject.___IS_TRACKED_OPTIMISED 用于判断未被@Track装饰的class和Object(Object包括所有字面量、new 创建的所有对象)
public static needsPropertyReadCb(obj: Object): boolean
用于判断是使用了@Track装饰了属性的对象
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方法
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属性关联的那些组件
PropertyDependencies
public getTrackedObjectPropertyDependencies(changedObjectProperty: string, debugInfo: string): Set
依赖某个属性的所有组件存在于一个Map中。key是属性的名称,value是依赖这个属性的组件的id组成的集合
我们看到依赖的组件id是通过 addTrackedObjectPropertyDependency 添加的。看下谁调用了这个方法
类似的逻辑可以参考 鸿蒙ACE-状态分析@State
protected recordTrackObjectPropertyDependencyForElmtId(renderingElmtId : number, readTrackedPropertyName : string) : void
那么调用这个方法的地方又有哪些呢?
ObservedPropertyPU
protected onOptimisedObjectPropertyRead(readObservedObject: T, readPropertyName: string, isTracked: boolean) : void
可以看到这个方法是一个回调,方法名很容易理解,被优化的属性读取时的回调,这里被优化的属性就是@Track装饰的属性。
再看一下 onOptimisedObjectPropertyRead 回调什么时候被调用
public get(): T
ok,我们再看一下shouldInstallTrackedObjectReadCb这个标识什么时候为true
private setValueInternal(newValue: T): boolean
我们发现在setValueInternal方法里面,会根据TrackedObject.needsPropertyReadCb的返回值将shouldInstallTrackedObjectReadCb置为true或false
TrackedObject.needsPropertyReadCb这个方法我们在上面看到过了,如果有这个对象里面有@Track属性,就会置为true
同样的我们这里还需要提一嘴,在ObservedPropertyPU的constructor和set方法里都会调用setValueInternal。所以组件一创建的时候shouldInstallTrackedObjectReadCb就是true了
总结
我们总结一下Track装饰器生效的过程
-
@Track装饰属性,编译完成后会给属性所在实例对象添加两个额外属性
- 一个是boolean属性TrackedObject.___IS_TRACKED_OPTIMISED,标志这个对象被Track装饰过
- 一个是boolean属性TrackedObject.___TRACKED_PREFIX+属性的本来名称。可以通过它来区分该属性是普通属性还是Track属性
-
@Track装饰的类是一个状态变量时
- 状态变量底层转换成ObservedPropertyObjectPU,在这个对象初始化时会将shouldInstallTrackedObjectReadCb置为true
- 之后我们在组件中通过get获取这个属性时,会将组件id保存在一个Map中,这个Map以属性名为key,依赖这个属性的所有组件id组成的Set为value
- 在我们通过set方法修改属性时,在Map中找到依赖这个属性的所有的组件id,然后将这些组件标记为dirty。然后再下次Vsync过来时,这些组件就会被更新
评论记录:
回复评论: