TS 装饰器与元数据

dfaa63c0abb4a20aac5e86f184f9dca9.png

类装饰器

类装饰器在类声明之前被声明(紧靠着类声明)。类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。类装饰器不能用在声明文件中( .d.ts),也不能用在任何外部上下文中(比如declare的类)。

类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。

如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。

注意 如果你要返回一个新的构造函数,你必须注意处理好原来的原型链。在运行时的装饰器调用逻辑中 不会为你做这些。

下面是一个重载构造函数的例子。

function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
  return class extends constructor {
      newProperty = "new property";
      hello = "override";
  }
}


@classDecorator
class Greeter {
  property = "property";
  hello: string;
  constructor(m: string) {
      this.hello = m;
  }
}


console.log(new Greeter("world"));
// { "property": "property", "hello": "override", "newProperty": "new property" }

上面代码中 Greeter 定义了两个属性 property 和 hello 两个值,但是经过 classDecorator 装饰器处理之后,属性变成了 newProperty,hello,property 三个值。

方法装饰器

方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。方法装饰器不能用在声明文件( .d.ts),重载或者任何外部上下文(比如declare的类)中。

方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

1.对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。2.成员的名字。3.成员的属性描述符。

下面是一个方法装饰器(@enumerable)的例子,应用于Greeter类的方法上:

function enumerable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
      descriptor.enumerable = value;
  };
}


class Greeter {
  greeting: string;
  constructor(message: string) {
      this.greeting = message;
  }


  @enumerable(true)
  greet() {
      return "Hello, " + this.greeting;
  }
}


console.log(Object.getOwnPropertyDescriptor(Greeter.prototype, 'greet'))
//{ "writable": true, "enumerable": true, "configurable": true }

这里的 @enumerable(false) 是一个装饰器工厂。当装饰器 @enumerable(true) 被调用时,它会修改属性描述符的 enumerable 属性,把原型上的方法 greet 变得可枚举,也就是通过可以 for-in 进行遍历得到。

属性装饰器

属性装饰器声明在一个属性声明之前(紧靠着属性声明)。属性装饰器不能用在声明文件中(.d.ts),或者任何外部上下文(比如 declare的类)里。

属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:

1.对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。2.成员的名字。

注意  属性描述符不会做为参数传入属性装饰器,这与TypeScript是如何初始化属性装饰器的有关。因为目前没有办法在定义一个原型对象的成员时描述一个实例属性,并且没办法监视或修改一个属性的初始化方法。返回值也会被忽略。因此,属性描述符只能用来监视类中是否声明了某个名字的属性。

import "reflect-metadata";


const formatMetadataKey = Symbol("format");


function format(formatString: string) {
    return Reflect.metadata(formatMetadataKey, formatString);
}


function getFormat(target: any, propertyKey: string) {
    return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}


class Greeter {
  @format("Hello, %s")
  greeting: string;


  constructor(message: string) {
      this.greeting = message;
  }
  greet() {
      let formatString = getFormat(this, "greeting");
      return formatString.replace("%s", this.greeting);
  }
}


let greeter = new Greeter('dog');
console.log(greeter.greet())
// Hello, dog

这个 @format("Hello, %s") 装饰器是个 装饰器工厂。当 @format("Hello, %s") 被调用时,它添加一条这个属性的元数据,通过reflect-metadata库里的 Reflect.metadata 函数。当 getFormat 被调用时,它读取格式的元数据。

注意  这个例子需要使用 reflect-metadata 库。元数据:实际上就是一组特别的常量,通常储存着有关 class 的各种信息,在运行时可读取这些信息做一些操作,目前主要由 reflect-metadata 库提供实现。

参数装饰器

参数装饰器声明在一个参数声明之前(紧靠着参数声明)。参数装饰器应用于类构造函数或方法声明。参数装饰器不能用在声明文件(.d.ts),重载或其它外部上下文(比如 declare的类)里。

参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

1.对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。2.成员的名字。3.参数在函数参数列表中的索引。

注意  参数装饰器只能用来监视一个方法的参数是否被传入。

import "reflect-metadata";


const requiredMetadataKey = Symbol("required");


function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
    let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
    existingRequiredParameters.push(parameterIndex);
    Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}


function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<any>) {
    let method: any = descriptor.value;
    descriptor.value = function () {
        let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);
        if (requiredParameters) {
            for (let parameterIndex of requiredParameters) {
                if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) {
                    throw new Error("Missing required argument.");
                }
            }
        }


        return method.apply(this, arguments);
    }
}


class Greeter {
  greeting: string;


  constructor(message: string) {
      this.greeting = message;
  }


  @validate
  greet(@required name: string) {
      return "Hello " + name + ", " + this.greeting;
  }
}


let greeter: any = new Greeter('dog');
greeter.greet()
// Uncaught Error: Missing required argument.

@required 装饰器添加了元数据实体把参数标记为必需的。@validate 装饰器把 greet 方法包裹在一个函数里在调用原先的函数前验证函数参数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值