harmonyNext系列二 从this指针示例看@state状态变量的实现原理
摘要
华为文档[1]中给出了一个关于使用@state的实际样例,并且指明了是由于this指针导致的状态变量的修改使UI并未生效,这个引发了我们的兴趣,于是我们通过查看编译生成的abc文件以及相关源码确认了@state状态变量的实现逻辑,并最终分析出为什么该样例下的状态变量操作没有生效
引言
在华为介绍@state的文档中有这样一个示例,并提到在该场景下, changeCoverUrl的this指VewModel,而不是被装饰器@State代理的状态变量。从而导致改变状态变量并未生效
export default class PlayDetailViewModel {
coverUrl: string = '#00ff00'
changeCoverUrl= ()=> {
this.coverUrl = '#00F5FF'
}
}
import PlayDetailViewModel from './PlayDetailViewModel'
@Entry
@Component
struct PlayDetailPage {
@State vm: PlayDetailViewModel = new PlayDetailViewModel()
build() {
Stack() {
Text(this.vm.coverUrl).width(100).height(100).backgroundColor(this.vm.coverUrl)
Row() {
Button('点击改变颜色')
.onClick(() => {
this.vm.changeCoverUrl()
})
}
}
.width('100%')
.height('100%')
.alignContent(Alignment.Top)
}
}
我们首先分析以上代码中的this指针实际指向
- PlayDetailViewModel中的箭头函数包含的this毫无疑问指向了运行时的他自身,也就是defined context
- 再看PlayDetailPage,这里有一个语法糖,build方法这里实际上并不是一个方法实现而是一个尾随闭包,也就是{}内部的内容其实是一个闭包参数传递给了build方法,因此这里的this其实也是指向playDetailPage自身。
使用IDE编译以上代码以后发现Button的颜色并未改变,因此我们可以推测,this.vm.changeCoverUrl()调用中this.vm肯定和model本身不是一个指针。换句话说,通过@state修饰后的变量vm已经和初始化时候的new PlayDetailViewModel()不是一个东西了
@state状态变量具体实现
为了能够了解@state的实现机制,我们只能寻求源码的支持,下面给出了通过hvigor构建编译后生成的源码以及华为给的openharmony源码库的实现
编译后生成的源码
private __vm: ObservedPropertyObjectPU<PlayDetailViewModel>;
get vm() {
return this.__vm.get();
}
set vm(newValue: PlayDetailViewModel) {
this.__vm.set(newValue);
}
this.observeComponentCreation2((elmtId, isInitialRender) => {
Button.createWithLabel('点击改变颜色');
Button.onClick(() => {
// let self = this.vm
// this.vm.changeCoverUrl(self)
this.vm.changeCoverUrl();
});
}, Button);
从编译后生成的源码我们完全可以看到 this.vm实际调用的方法就是this.__vm.get(),这个私有的__vm是一个ObservedPropertyObjectPU的泛型,因此我们需要去知道get方法是如何实现的.
ObservedPropertyObjectPU源码
openharmony关于ObservedPropertyObjectPU的实现源码[2]:
setValueInternal(newValue) {
if (typeof newValue !== 'object') {
return false;
}
this.unsubscribeFromOwningProperty();
if (ObservedObject.IsObservedObject(newValue)) {
ObservedObject.addOwningProperty(newValue, this);
this.wrappedValue_ = newValue;
}
else if (newValue instanceof SubscribaleAbstract) {
this.wrappedValue_ = newValue;
this.wrappedValue_.addOwningProperty(this);
}
else {
this.wrappedValue_ = ObservedObject.createNew(newValue, this);
}
return true;
}
get() {
this.notifyPropertyHasBeenReadPU();
return this.wrappedValue_;
}
class ObservedObject extends ExtendableProxy {
......
}
class ExtendableProxy {
constructor(obj, handler) {
return new Proxy(obj, handler);
}
}
也就是说返回给上层的this.vm其实是一个this.wrappedValue_,看赋值逻辑这个this.wrappedValue_最终指向ObservedObject.createNew(newValue, this),也就是一个Proxy对象。
proxy对象
我还没有参透proxy的内部实现细节就不介绍了,留一个定义在这里
Proxy 对象是ES6新出的一个特性,用于创建一个对象的代理,从而实现基本操作的拦截和自定义,如属性查找、赋值、枚举、函数调用等
所以传递给this.vm实际上是一个proxy对象,this.vm.changeCoverUrl箭头函数里面只会去修改原始的viewmodel的值,而不会对proxy造成影响,而实现状态自动刷新UI的逻辑都被封装到了proxy对象的方法里面了,类似于这样。
let p = new Proxy(obj, {
get(target, property) {
console.log("我拦截了" + target + "读取" + property);
console.log("它的值为" + target[property]);// 定义你要返回的值
return target[property];
},
});
总结
本文从一个@state的示例出发,解释了状态变量的实现逻辑,底层通过构建一个Proxy对象完成对外部变量的一个封装,并起到一个拦截以及自定义方法的作用。
引用
转载自:https://juejin.cn/post/7370841518791311395