likes
comments
collection
share

harmonyNext系列二 从this指针示例看@state状态变量的实现原理

作者站长头像
站长
· 阅读数 40

摘要

华为文档[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对象完成对外部变量的一个封装,并起到一个拦截以及自定义方法的作用。

引用

  1. developer.huawei.com/consumer/cn…
  2. gitee.com/arkui-finla…
转载自:https://juejin.cn/post/7370841518791311395
评论
请登录