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

什么是装饰器

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

装饰器分类

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

装饰器简单代码示例

@ClassDecorator() // 类装饰器
class A {

  @PropertyDecorator() // 属性装饰器
  name: string;

  @MethodDecorator() // 方法装饰器
  fly(
    @ParameterDecorator() // 参数装饰器
    meters: number
  ) {
    // code
  }

  @AccessorDecorator() // 存取器装饰器
  get egg() {
    // code
  }
  set egg(e) {
    // code
  }
}
  1. 注意,构造方法没有方法装饰器,只有参数装饰器。类装饰器其实就是在装饰构造方法。
  2. 装饰器只能用于类,要么应用于类的整体,要么应用于类的内部成员,不能用于独立的函数

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

类型定义

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

示例

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

@Log('log something')
class A {}

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

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

类型定义

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

示例

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

function ValidRange(min:number, max:number) {
  return (target:Object, key:string) => {
    Object.defineProperty(target, key, {
      set: function(v:number) {
        if (v < min || v > max) {
          throw new Error(`Not allowed value ${v}`);
        }
      }
    });
  }
}

// 输出 Installing ValidRange on year
class Student {
  @ValidRange(1920, 2020)
  year!: number;
}

const stud = new Student();

// 报错 Not allowed value 2022 
stud.year = 2022;

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

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

类型定义

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

示例

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

function logger(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value;

  descriptor.value = function (...args) {
    console.log('params: ', ...args);
    const result = original.call(this, ...args);
    console.log('result: ', result);
    return result;
  }
}

class C {
  @logger
  add(x: number, y:number ) {
    return x + y;
  }
}

(new C()).add(1, 2)
// params:  1 2
// result:  3

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

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

类型定义

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

示例

function log(
  target: Object,
  propertyKey: string|symbol,
  parameterIndex: number
) {
  console.log(`${String(propertyKey)} NO.${parameterIndex} Parameter`);
}

class C {
  member(
    @log x:number,
    @log y:number
  ) {
    console.log(`member Parameters: ${x} ${y}`);
  }
}

const c = new C();
c.member(5, 5);
// member NO.1 Parameter
// member NO.0 Parameter 
// member Parameters: 5 5 

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

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

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

类型定义

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

示例

 
function validator(
  target: Object,
  propertyKey: string,
  descriptor: PropertyDescriptor
){
  const originalGet = descriptor.get;
  const originalSet = descriptor.set;
  
  if (originalSet) {
    descriptor.set = function (val) {
      if (val > 100) {
        throw new Error(`Invalid value for ${propertyKey}`);
      }
      originalSet.call(this, val);
    };
  }
}

class C {
  #foo!: number;

  @validator
  set foo(v) {
    this.#foo = v;
  }

  get foo() {
    return this.#foo;
  }
}

const c = new C();
c.foo = 150;
// 报错

装饰器的执行顺序

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

  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

看一下构造函数

 
/**
   * Create a View
   *
   * 1. option: top level View, specify
   *    - compilerAssignedUniqueChildId must specify
   *    - parent=undefined
   *    - localStorage  must provide if @LocalSTorageLink/Prop variables are used
   *      in this View or descendant Views.
   *
   * 2. option: not a top level View
   *    - compilerAssignedUniqueChildId must specify
   *    - parent must specify
   *    - localStorage do not specify, will inherit from parent View.
   *
  */
  constructor(parent: ViewPU, localStorage: LocalStorage, elmtId : number = -1, extraInfo : ExtraInfo = undefined) {
    super();
    // if set use the elmtId also as the ViewPU object's subscribable id.
    // these matching is requiremrnt for updateChildViewById(elmtId) being able to
    // find the child ViewPU object by given elmtId
    this.id_= elmtId == -1 ? SubscriberManager.MakeId() : elmtId;
    this.providedVars_ = parent ? new Map(parent.providedVars_)
      : new Map<string, ObservedPropertyAbstractPU<any>>();

    this.localStoragebackStore_ = undefined;
    stateMgmtConsole.log(`ViewPU constructor: Creating @Component '${this.constructor.name}' from parent '${parent?.constructor.name}}'`);
    if (extraInfo) {
      this.extraInfo_ = extraInfo;
    }
    if (parent) {
      // this View is not a top-level View
      this.setCardId(parent.getCardId());
      // Call below will set this.parent_ to parent as well
      parent.addChild(this);
    } else if (localStorage) {
      this.localStorage_ = localStorage;
      stateMgmtConsole.debug(`${this.debugInfo()}: constructor: Using LocalStorage instance provided via @Entry.`);
    }

    SubscriberManager.Add(this);
    stateMgmtConsole.debug(`${this.debugInfo()}: constructor: done`);
  }

上面的注释写明了,@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

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

  • 26
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值