当前位置:   article > 正文

鸿蒙HarmonyOS应用开发-窥探:State装饰器_viewpu

viewpu

什么是装饰器

  1. 装饰器是一个函数,这个函数仅在代码加载阶段执行一次。本质就是编译时执行的函数
  2. 装饰器的语法是 @后跟一个函数或者一个执行后返回函数的表达式
  3. 这个函数要么不返回值,要么返回一个新对象取代所修饰的目标对象
  4. 装饰器有两个版本,一个是2014年通过的,一个是2022年通过的。ArkTS里使用的是2014年通过的

装饰器分类

我们介绍的也只是2014年通过的。如果想了解最新版的装饰器,请看 TypeScript 装饰器

装饰器简单代码示例

  1. @ClassDecorator() // 类装饰器
  2. class A {
  3. @PropertyDecorator() // 属性装饰器
  4. name: string;
  5. @MethodDecorator() // 方法装饰器
  6. fly(
  7. @ParameterDecorator() // 参数装饰器
  8. meters: number
  9. ) {
  10. // code
  11. }
  12. @AccessorDecorator() // 存取器装饰器
  13. get egg() {
  14. // code
  15. }
  16. set egg(e) {
  17. // code
  18. }
  19. }
  1. 注意,构造方法没有方法装饰器,只有参数装饰器。类装饰器其实就是在装饰构造方法。
  2. 装饰器只能用于类,要么应用于类的整体,要么应用于类的内部成员,不能用于独立的函数

类装饰器(Class Decorators):用于类

类型定义

  • 类型参数TFunction必须是函数,实际上就是构造方法。
  • 类装饰器的返回值,要么是返回处理后的原始构造方法,要么返回一个新的构造方法
  1. type ClassDecorator = <TFunction extends Function>
  2. (target: TFunction) => TFunction | void;

示例

  1. 下面定义的Log函数,通过在前面使用@修饰,并将它应用于class A。这样创建A的实例的时候,就会输出log
  2. 这段代码应用了高级函数的知识。即在Log函数里返回了一个类装饰器函数
  1. function Log(info:string) {
  2. console.log('received: ', info);
  3. return function (target:any) {
  4. console.log('apply decorator');
  5. return target;
  6. }
  7. }
  8. @Log('log something')
  9. class A {}

属性装饰器(Property Decorators):用于属性

属性装饰器不需要返回值,如果有的话,也会被忽略

类型定义

  • target:(对于实例属性)类的原型对象(prototype),或者(对于静态属性)类的构造函数。
  • propertyKey:所装饰属性的属性名,注意类型有可能是字符串,也有可能是 Symbol 值
 
  1. type PropertyDecorator =
  2. (
  3. target: Object,
  4. propertyKey: string|symbol
  5. ) => void;

示例

装饰器ValidRange对属性year设立了一个上下限检查器,只要该属性赋值时,超过了上下限,就会报错

  1. function ValidRange(min:number, max:number) {
  2. return (target:Object, key:string) => {
  3. Object.defineProperty(target, key, {
  4. set: function(v:number) {
  5. if (v < min || v > max) {
  6. throw new Error(`Not allowed value ${v}`);
  7. }
  8. }
  9. });
  10. }
  11. }
  12. // 输出 Installing ValidRange on year
  13. class Student {
  14. @ValidRange(1920, 2020)
  15. year!: number;
  16. }
  17. const stud = new Student();
  18. // 报错 Not allowed value 2022
  19. stud.year = 2022;

方法装饰器(Method Decorators):用于方法

方法装饰器用来装饰类的方法。方法装饰器的返回值(如果有的话),就是修改后的该方法的描述对象,可以覆盖原始方法的描述对象

类型定义

  • target:(对于类的静态方法)类的构造函数,或者(对于类的实例方法)类的原型。
  • propertyKey:所装饰方法的方法名,类型为string|symbol
  • descriptor:所装饰方法的描述对象
  1. type MethodDecorator = <T>(
  2. target: Object,
  3. propertyKey: string|symbol,
  4. descriptor: TypedPropertyDescriptor<T>
  5. ) => TypedPropertyDescriptor<T> | void;

示例

方法装饰器@logger用来装饰add()方法,它的作用是让该方法输出日志。每当add()调用一次,控制台就会打印出参数和运行结果

  1. function logger(
  2. target: any,
  3. propertyKey: string,
  4. descriptor: PropertyDescriptor
  5. ) {
  6. const original = descriptor.value;
  7. descriptor.value = function (...args) {
  8. console.log('params: ', ...args);
  9. const result = original.call(this, ...args);
  10. console.log('result: ', result);
  11. return result;
  12. }
  13. }
  14. class C {
  15. @logger
  16. add(x: number, y:number ) {
  17. return x + y;
  18. }
  19. }
  20. (new C()).add(1, 2)
  21. // params: 1 2
  22. // result: 3

参数装饰器(Parameter Decorators):用于方法的参数

该装饰器不需要返回值,如果有的话会被忽略

类型定义

  • target:(对于静态方法)类的构造函数,或者(对于类的实例方法)类的原型对象。
  • propertyKey:所装饰的方法的名字,类型为string|symbol
  • parameterIndex:当前参数在方法的参数序列的位置(从0开始)
 
  1. type ParameterDecorator = (
  2. target: Object,
  3. propertyKey: string|symbol,
  4. parameterIndex: number
  5. ) => void;

示例

  1. function log(
  2. target: Object,
  3. propertyKey: string|symbol,
  4. parameterIndex: number
  5. ) {
  6. console.log(`${String(propertyKey)} NO.${parameterIndex} Parameter`);
  7. }
  8. class C {
  9. member(
  10. @log x:number,
  11. @log y:number
  12. ) {
  13. console.log(`member Parameters: ${x} ${y}`);
  14. }
  15. }
  16. const c = new C();
  17. c.member(5, 5);
  18. // member NO.1 Parameter
  19. // member NO.0 Parameter
  20. // member Parameters: 5 5

存取器装饰器(Accessor Decorators):用于类的 set 或 get 方法

存取器装饰器用来装饰类的存取器(accessor)。所谓“存取器”指的是某个属性的取值器(getter)和存值器(setter)

TypeScript 不允许对同一个属性的存取器(getter 和 setter)使用同一个装饰器,也就是说只能装饰两个存取器里面的一个,且必须是排在前面的那一个,否则报错

类型定义

  • target:(对于静态属性的存取器)类的构造函数,或者(对于实例属性的存取器)类的原型。
  • propertyKey:存取器的属性名。
  • descriptor:存取器的属性描述对象
  1. type AccessorDecorator = <T>(
  2. target: Object,
  3. propertyKey: string|symbol,
  4. descriptor: TypedPropertyDescriptor<T>
  5. ) => TypedPropertyDescriptor<T> | void;

示例

 
  1. function validator(
  2. target: Object,
  3. propertyKey: string,
  4. descriptor: PropertyDescriptor
  5. ){
  6. const originalGet = descriptor.get;
  7. const originalSet = descriptor.set;
  8. if (originalSet) {
  9. descriptor.set = function (val) {
  10. if (val > 100) {
  11. throw new Error(`Invalid value for ${propertyKey}`);
  12. }
  13. originalSet.call(this, val);
  14. };
  15. }
  16. }
  17. class C {
  18. #foo!: number;
  19. @validator
  20. set foo(v) {
  21. this.#foo = v;
  22. }
  23. get foo() {
  24. return this.#foo;
  25. }
  26. }
  27. const c = new C();
  28. c.foo = 150;
  29. // 报错

装饰器的执行顺序

执行装饰器时,按照如下顺序执行。

  1. 实例相关的装饰器。
  2. 静态相关的装饰器。
  3. 构造方法的参数装饰器。
  4. 类装饰器
  5. 同一级装饰器的执行顺序,是按照它们的代码顺序。但是,参数装饰器的执行总是早于方法装饰器
  6. 如果同一个方法或属性有多个装饰器,那么装饰器将顺序加载、逆序执行
  7. 如果同一个方法有多个参数,那么参数也是顺序加载、逆序执行

@State装饰器探索

@State装饰器只能应用于组件内部,并且它修饰的是属性。从这些特征我们就能知道它是一个属性装饰器

编译成包

我们知道鸿蒙工程是一个多Module的工程,开发态和编译后的包对应视图如下

image.png

看一下我们目前的代码工程

image.png

进行代码编译成包

image.png

查看编译后的结构。可以看到我们编译成了一个.app和一个.hap

image.png

解包HAP

右键entry的hap,open in finder

image.png

image.png

复制一份.hap文件,并修改为.zip后缀,然后双击打开

image.png

ets文件夹里面的就是方舟字节码文件,使用010Editor进行解析(很抱歉,我没学会怎么用,借了一张大神的图).看到如下图所示

image.png

当我们用@Component修饰一个struct的时候,通过ArkCompiler编译后,其实会生成一个类,这个类继承于ViewPU

arkui_ace_engine源码 查看State

ViewPU定义在ArkTS framework arkui_ace_engine当中,是Openharmony中UI承载的关键类

ViewPU定义

image.png

看一下构造函数

 
  1. /**
  2. * Create a View
  3. *
  4. * 1. option: top level View, specify
  5. * - compilerAssignedUniqueChildId must specify
  6. * - parent=undefined
  7. * - localStorage must provide if @LocalSTorageLink/Prop variables are used
  8. * in this View or descendant Views.
  9. *
  10. * 2. option: not a top level View
  11. * - compilerAssignedUniqueChildId must specify
  12. * - parent must specify
  13. * - localStorage do not specify, will inherit from parent View.
  14. *
  15. */
  16. constructor(parent: ViewPU, localStorage: LocalStorage, elmtId : number = -1, extraInfo : ExtraInfo = undefined) {
  17. super();
  18. // if set use the elmtId also as the ViewPU object's subscribable id.
  19. // these matching is requiremrnt for updateChildViewById(elmtId) being able to
  20. // find the child ViewPU object by given elmtId
  21. this.id_= elmtId == -1 ? SubscriberManager.MakeId() : elmtId;
  22. this.providedVars_ = parent ? new Map(parent.providedVars_)
  23. : new Map<string, ObservedPropertyAbstractPU<any>>();
  24. this.localStoragebackStore_ = undefined;
  25. stateMgmtConsole.log(`ViewPU constructor: Creating @Component '${this.constructor.name}' from parent '${parent?.constructor.name}}'`);
  26. if (extraInfo) {
  27. this.extraInfo_ = extraInfo;
  28. }
  29. if (parent) {
  30. // this View is not a top-level View
  31. this.setCardId(parent.getCardId());
  32. // Call below will set this.parent_ to parent as well
  33. parent.addChild(this);
  34. } else if (localStorage) {
  35. this.localStorage_ = localStorage;
  36. stateMgmtConsole.debug(`${this.debugInfo()}: constructor: Using LocalStorage instance provided via @Entry.`);
  37. }
  38. SubscriberManager.Add(this);
  39. stateMgmtConsole.debug(`${this.debugInfo()}: constructor: done`);
  40. }

上面的注释写明了,@Component装饰的视图作为顶级视图和非顶级视图的情况

  1. 顶级视图选项:

    • 必须指定compilerAssignedUniqueChildId,用于唯一标识子视图。
    • parent设置为undefined,表示这是顶级视图。
    • 如果在这个视图或其子视图中使用了@LocalStorageLink/Prop变量,必须提供localStorage
  2. 非顶级视图选项:

    • 必须指定compilerAssignedUniqueChildId,用于唯一标识子视图。
    • 必须指定parent,指明父级视图。
    • 不需要指定localStorage,它将从父视图继承。

看一下最后的代码 它调用SubscriberManager添加自身,后面state的回调会进行callback,同时它也有父子组件的概念,ViewPU有一个parent属性,代表当前的父组件,父子双方共用同一个localStorage,其实就是通过构造函数保证的。

搜索implementation of @State

notifyPropertyHasChangedPU

我们看到属性改变的时候,调用了他所属view的viewPropertyHasChanged

image.png

viewPropertyHasChanged

image.png

今天先到这。后面理顺了再更新下

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/297789
推荐阅读
相关标签
  

闽ICP备14008679号