likes
comments
collection
share

深入浅出Typescript装饰器与Reflect元数据

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

hey🖐! 我是小黄瓜😊😊。不定期更新,期待关注➕ 点赞,共同成长~

装饰器是什么?

在js中,其实装饰器是一个很少用到的概念。装饰器就是一个方法或者叫函数,可以注入/写到到类、方法、属性、参数,对象上,扩展其功能。

目前在js也有一个提案将其引入了 ECMAScript。 在TypeScript 中的装饰器目前只能在类以及类成员上使用。 ts装饰器通过 @ 语法来使用:

function Deco() { }

@Deco
class Foo {}

装饰器两种写法

在定义装饰器的时候,通常有两种方式,一种是直接将函数作为装饰器函数:

function ClassDecorator() {
  // do something ...
}

@ClassDecorator
class CustomerService {
  name: string = 'dog'
  constructor() {}
}

第二种是装饰器工厂函数,在调用时可以传递参数:

function ClassDecorator(info: any) {
  return () => {
    // do something ...
  }
}

@ClassDecorator('刺')
class CustomerService {
  name: string = '小黄瓜'
  constructor() {}
}

在装饰器工厂函数中,首先会执行ClassDecorator() ,然后在使用返回的匿名函数作为装饰器的实际逻辑。

装饰器的分类

TypeScript 中的装饰器可以分为类装饰器方法装饰器访问符装饰器属性装饰器以及参数装饰器五种。

类装饰器

类装饰器是直接作用在类上的装饰器,在执行时只有一个入参,那就是这个类本身: 参数:

  • target:类本身
// 直接调用
function ClassDecorator(target: any) {
  console.log(target)
}

// 工厂函数
function SecondClassDecorator(info: string): ClassDecorator {
  return (target: any) => {
    console.log(info + ':' + target.prototype)
  }
}

@ClassDecorator
@SecondClassDecorator('工厂函数')
class CustomerService {
  static addr = '小黄瓜'
  public name!: string
  constructor() {
    this.name = '瓜瓜'
  }
  sayAddr() {
    console.log(this.name)
  }
}

我们在第一个装饰器函数中直接打印了参数target,将会返回类本身,为了验证我们又使用工厂函数SecondClassDecorator自定义了一个参数,然后返回真正应用的装饰器函数用于获取原型对象,也可以正确的获取到。

SecondClassDecorator函数中我们将定义返回值定义为了ClassDecorator这是装饰器内置的一个类型,约束类装饰器应用函数,它的内部是这样定义的:

declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;

可以看到ClassDecorator类型将泛型TFunction约束为函数,因为类也是函数,接收target参数,可以选择无返回值,或者函数类型。函数类型?这也就意味着可以返回一个子类。

function ClassDecorator<T extends { new (...args: any[]): any }>(target: T) {
  // 匿名类
  return class extends CustomerService {
    commonMethod() {
        console.log('子类:', this.name)
    }
  }
}

@ClassDecorator
class CustomerService {
  public name!: string
  constructor() {
    this.name = '瓜瓜'
  }
  sayAddr() {
    console.log('父类:', this.name)
  }
}

new CustomerService().sayAddr() // "父类:",  "瓜瓜" 
23
(new CustomerService() as any).commonMethod() // "子类:",  "瓜瓜" 

CustomerService类的装饰器函数ClassDecorator中我们返回了一个子类,在父类的实例中可以调用子类的方法,可以在子类中加入一下通用逻辑。

function ClassDecorator<T extends { new (...args: any[]): any }>(target: T) {
  return class extends CustomerService {
    constructor(...args: any[]) {
        super(args)
        console.log('哈哈我来了,我是子类', this.name)
    }
  }

}

@ClassDecorator
class CustomerService {
  public name!: string
  constructor(...args: any[]) {
    console.log('哈哈我来了,我是父类')
    this.name = '瓜瓜'
  }
}
let p = new CustomerService()

// 哈哈我来了,我是父类
// 哈哈我来了,我是子类 瓜瓜

我们可以在ClassDecorator装饰器函数返回的子类创建一些公用的逻辑,用于在不同的类中进行装饰器的注册,此时两个类的执行顺序为 父类 > 子类,在子类中还可以获取父类的属性和方法。

例如我们可以使用这种方式实现一个类调用的日志记录,通过装饰器参数target来实现记录类的信息。 也正是由于这种特性,装饰器返回的子类可以覆盖父类中的方法:

function ClassDecorator<T extends { new (...args: any[]): any }>(target: T) {
  // 返回一个新的类
  return class extends CustomerService {
    constructor(...args: any[]) {
        super(args)
    }
    sayName() {
        console.log('我要篡位!')
    }
  }
}

@ClassDecorator
class CustomerService {
  public name!: string
  constructor(...args: any[]) {
    this.name = '瓜瓜'
  }
  sayName() {
    console.log(this.name)
  }
}

let p = new CustomerService()
p.sayName() // '我要篡位!'

子类中定义了与父类相同的方法,父类中的方法被子类中的同名方法覆盖掉了。

方法装饰器

方法装饰器顾名思义就是定义在类中方法上的装饰器。它接收三个参数:

  • targetClassPrototype:类的原型
  • methodName: 方法名
  • propertyDescriptor: 属性描述符
function MyMethodDecorator(info: string): MethodDecorator {
    return (targetClassPrototype: any, methodName: any, propertyDescriptor: PropertyDescriptor) => {  
      console.log(targetClassPrototype) // CustomerService: {} 
      console.log(methodName) // distribRoles
      console.log(propertyDescriptor) 
        // {
        //   "writable": true,
        //   "enumerable": false,
        //   "configurable": true
        //   "value": () => {}
        // } 
    }
}

class CustomerService {
  public name!: string
  constructor(...args: any[]) {
    this.name = '瓜瓜'
  }

  @MyMethodDecorator('小黄瓜')
  distribRoles() {
    console.log('执行原方法~')
  }
}

接收的第三个参数为属性描述符,这也就为我们修改原方法提供了可能,先此之前,首先来看一下方法装饰器的类型是怎样定义的?

declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;

TypedPropertyDescriptor与我们在下面使用的PropertyDescriptor接口实现基本一致,都是为descript属性描述符提供一个类型接口,只不过一个支持泛型传入:

interface PropertyDescriptor {
    configurable?: boolean;
    enumerable?: boolean;
    value?: any;
    writable?: boolean;
    get?(): any;
    set?(v: any): void;
}

interface TypedPropertyDescriptor<T> {
    enumerable?: boolean;
    configurable?: boolean;
    writable?: boolean;
    value?: T;
    get?: () => T;
    set?: (value: T) => void;
}

借由属性描述符,我们可以实现对方法的代理拦截:

function MyMethodDecorator(info: string): MethodDecorator {
    return (targetClassPrototype: any, methodName: any, decri: PropertyDescriptor) => {
        // 保存原始方法
        let datapropsmethod = decri.value

        decri.value = function(...args: any) {
            console.log('前置拦截')
            // do something...
            // 执行原方法
            datapropsmethod.call(this, args)
            // // do something...
            console.log('后置拦截')
        }
    }
}

class CustomerService {
  public name!: string
  constructor(...args: any[]) {
    this.name = '瓜瓜'
  }
  @MyMethodDecorator('小黄瓜')
  distribRoles() {
    console.log('执行原方法~')
  }
}

let p = new CustomerService()
p.distribRoles()

在方法装饰器中,我们首先使用属性描述符获取value属性,也就是原方法,将它保存到一个变量中,然后重写属性描述符的value属性,这样当执行这个方法时,执行的就是我们新定义的这个方法。在新方法中进行一些额外操作。

[LOG]: "前置拦截" 
[LOG]: "执行原方法~" 
[LOG]: "后置拦截" 

这里还有一个关于属性描述符需要注意的点,直接获取属性的value更改是不会生效的,比如:

// 更改方法失效
class Customer {
    constructor() {}

    distribRoles() {
        console.log('分配~')
    }
}

let dataprops = Object.getOwnPropertyDescriptor(Customer.prototype, 'distribRoles')!
let datamethod = dataprops.value

dataprops.value = function(...args: any[]) {
    console.log('更改后的method')
    datamethod.call(this, args)
}

let p = new Customer()
p.distribRoles() // 分配~

调用distribRoles方法后发现执行的还是原函数,这里我们通过Object.getOwnPropertyDescriptor获取了方法的属性描述,然后通过value属性更改了原方法,发现并没有生效。

let dataprops = Object.getOwnPropertyDescriptor(Customer.prototype, 'distribRoles')!
let dataprops2 = Object.getOwnPropertyDescriptor(Customer.prototype, 'distribRoles')
// 开辟出来两份完全不同的内存空间
console.log(dataprops === dataprops2) // false

分别调用两次Object.getOwnPropertyDescriptor获取属性描述符,发现是两份完全不同的空间,其实可以这样理解,通过Object.getOwnPropertyDescriptor获取到属性描述对象后重新开启了一份新的内存空间进行存储,而我们直接使用value属性只是更改了新内存空间的方法,所以不会生效。

所以我们还需要调用Object.defineProperty来使用新的对象来覆盖原有的属性描述对象。

let dataprops = Object.getOwnPropertyDescriptor(Customer.prototype, 'distribRoles')

dataprops.value = function(...args) {
    console.log('更改后的method')
    datamethod.call(this, args)
}
// 重新设置
Object.defineProperty(Customer.prototype, 'distribRoles', dataprops)

属性装饰器

属性装饰器可以定义在类的属性上,它的参数有两个:

  • targetClassPrototype:类的原型
  • attrname:属性名
function loginProperty(attrVal:string): PropertyDecorator {
    return (targetClassPrototype: Record<string | symbol, any>, attrname: string | symbol) => {
        console.log(targetClassPrototype); // CustomerService: {} 
        console.log(attrname); // custname
        
        targetClassPrototype[attrname] = attrVal
        targetClassPrototype['otherName'] = '小明'
    }
}

class CustomerService {
    @loginProperty('小黄瓜')
    public custname!: string
    constructor() {}
}

let p = new CustomerService()
console.log(p.custname) // 小黄瓜
console.log(p.otherName) // 小明

属性装饰器返回函数的类型定义:

declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;

我们在类属性装饰器中为属性custname赋值,并且在类原型上增加了一个新的值,可以看到,通过实例调用已经成功输出。

方法参数装饰器

定义在方法参数上的装饰器,接收三个参数,分别为:

  • targetClassPrototype:类原型
  • methodName:方法名
  • paramIndex:参数位置(下标)
function UrlParame(params: any): ParameterDecorator {
  return (targetClassPrototype: object, methodName: string | symbol, paramIndex: number) => {
    console.log(targetClassPrototype) // People: {} 
    console.log(methodName) // eat
    console.log(paramIndex) // 0
  }
}

class People {
  eat(@UrlParame('地址信息') address: string, who: string) {
    console.log('address', address)
  }
}

其中方法参数装饰器的类型定义如下:

declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

构造器参数装饰器

构造器参数装饰器是定义在类的constructoe函数中,它接收三个参数如下:

  • targetClass:类本身,与其他的装饰器不同
  • paramName:undefined
  • paramerIndex:参数下标
function InjectConstructor(instanceName: string): ParameterDecorator {
    return (targetClass, paramName, paramerIndex) => {
        console.log(targetClass) 
      	// class userService {
        //     constructor(userService, count) {
        //         this.userService = userService;
        //         this.count = count;
        //     }
        // } 
        console.log(paramName) // undefined
        console.log(paramerIndex) // 0
    }
}

// customer
class userService {
    constructor(@InjectConstructor('userService') private userService: string, private count: number) {}
}

构造器参数装饰器与其他的装饰器略有不同,它的第一个参数返回类本身,并非类的原型对象。

执行顺序

不同装饰器应用函数

上文中我们定义了各种类,属性,方法,参数装饰器,那么他们如果定义在同一个类中,执行顺序是什么样的呢?

@classDecorator
class Test {
    constructor(@argDecorator age: number) {}

    @firstMethodDecorator
    say(@firstArgDecorator pname: string) {}
  
    @properDecorator
    public name: string = '小黄瓜'
  
    @secondMethodDecorator
    print(@secondArgDecorator addr: string) {}
}

function classDecorator(target: object) {
    console.log('执行类装饰器')
}
function argDecorator(classFunc: any, parameFunc: any, parameIndex: any) {
    console.log('执行构造器参数装饰器')
}

function properDecorator(classPrototype: any, attrName: any) {
    console.log('执行属性装饰器')
}

function firstArgDecorator(classPrototype: any, methodName: any, parameIndex: any) {
    console.log('第一个方法参数装饰器')
}
function secondArgDecorator(classPrototype: any, methodName: any, parameIndex: any) {
    console.log('第二个方法参数装饰器')

}

function firstMethodDecorator(classPrototype: any, methodName: any, desc: any) {
    console.log('第一个方法装饰器')
}
function secondMethodDecorator(classPrototype: any, methodName: any, desc: any) {
    console.log('第二个方法装饰器')
}

执行后的结果如下:

[LOG]: "第一个方法参数装饰器" 
[LOG]: "第一个方法装饰器" 
[LOG]: "执行属性装饰器" 
[LOG]: "第二个方法参数装饰器" 
[LOG]: "第二个方法装饰器" 
[LOG]: "执行构造器参数装饰器" 
[LOG]: "执行类装饰器" 

可以看到在定义的应用函数装饰器中,方法参数装饰器执行的优先级最高,而方法和属性装饰器受到定义顺序的影响。类与构造器参数装饰器优先级最低。

不同装饰器工厂函数

在定义装饰器工厂函数时,首先会执行工厂函数,然后才会将返回的函数作为其应用函数执行。

function TestFn(info: string): any {
  console.log(`${info} 执行`);
  return function () {
    console.log(`${info} 应用`);
  };
}

@TestFn('类')
class Person {
  constructor(@TestFn('构造函数参数') name: string) {}

  @TestFn('属性')
  prop?: number;

  @TestFn('方法')
  handler(@TestFn('方法参数') args: any) {}
}

接下来看一下执行结果:

[LOG]: "属性 执行" 
[LOG]: "属性 应用" 
[LOG]: "方法 执行" 
[LOG]: "方法参数 执行" 
[LOG]: "方法参数 应用" 
[LOG]: "方法 应用" 
[LOG]: "类 执行" 
[LOG]: "构造函数参数 执行" 
[LOG]: "构造函数参数 应用" 
[LOG]: "类 应用" 

可以看到属性和方法的优先级依然是最高的,其中在方法中包含参数装饰器时,方法开始执行后,会先将方法中参数装饰器全部执行应用完毕后,再应用方法装饰器。 构造函数参数和类装饰器优先级依然最低,这也确保了我们可以在类装饰器中获取其他方法,属性装饰器应用后的结果。

多个同类型装饰器

如果在同一个方法或者属性中定义多个装饰器呢?它们会以怎样的顺序执行呢?

function Test1(): any {
  console.log(`test1执行`);
  return function () {
    console.log(`test1应用`);
  };
}

function Test2(): any {
  console.log(`test2执行`);
  return function () {
    console.log(`test2应用`);
  };
}

function Test3(): any{
  console.log(`test3应用`);
}


function Test4(): any {
  console.log(`test4执行`);
  return function () {
    console.log(`test4应用`);
  };
}



class Person {
  constructor(name: string) {}

  prop?: number;
  @Test1()
  @Test2()
  @Test3
  @Test4()
  handler(args: any) {}
}

执行结果为:

[LOG]: "test1执行" 
[LOG]: "test2执行" 
[LOG]: "test4执行" 
[LOG]: "test4应用" 
[LOG]: "test3应用" 
[LOG]: "test2应用" 
[LOG]: "test1应用" 

装饰器的执行顺序符合我们的预期,遵循定义的顺序进行执行。但是应用顺序?好像与我们定义的顺序正好相反。是的,如果有多个同类装饰器作用于一个方法上,它的应用顺序是从下往上执行的。

深入浅出Typescript装饰器与Reflect元数据

元数据

元数据是指使用Reflect(反射)来保存和获取一些用于描述数据的信息。比如方法的参数信息、返回值信息等。而目前 ECMAScript 依然没有支持这种语法,所以在使用的时候需要安装并且引入reflect-metadata

import "reflect-metadata"

Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个。 (1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。 (2) 修改某些Object方法的返回结果,让其变得更合理。

而对于反射元数据来说,就是在Reflect上提供了几个方法,用于存取数据相关的信息。

// 为类或者对象上定义元数据
Reflect.defineMetadata(key, value, classObject)
// 为方法定义元数据
Reflect.defineMetadata(key, value, classPrototype, methodName)
// 为属性定义元数据
Reflect.defineMetadata(key, value, classPrototype, propKey)

Reflect.defineMetadata的前两个参数分别是保存数据的keyvalue,后面的参数分别是保存的路径,比如上面的后两个例子分别是将数据保存到classPrototype[methodName]classPrototype[propKey]

在普通对象中定义

在对象上使用一下:

let obj = {
    name: '小黄瓜'
}

// 定义在obj对象上
Reflect.defineMetadata('objTest1Key', 'test1Value', obj)
Reflect.defineMetadata('objTest2Key', 'test2Value', obj)

Reflect.getMetadata('objTest1Key', obj) // "test1Value"
// 不存在的定义
console.log(Reflect.getMetadata('test', obj)) // undefined

使用defineMetadata来为obj对象添加了keyobjTest1Key的值test1Value,然后使用getMetadata来进行获取,通过key取值,如果key不存在,会返回undefined

如果想要在对象中的属性上定义呢,比如想要定义在obj对象的name属性上。

// 定义在对象的属性上
Reflect.defineMetadata('propTest1Key', 'test1Value', obj, name)
Reflect.defineMetadata('propTest2Key', 'test2Value', obj, name)

Reflect.getMetadata('propTest1Key', obj, name) // test1Value
Reflect.getMetadata('propTest2Key', obj, name) // test2Value

通过传递defineMetadatagetMetadata方法的参数(依次传递对象名,属性名),来为对象或者属性定义或这获取元数据。

如果想要提前判断元数据存不存在怎么办?当然也为我们贴心的提供了检测方法:

if(Reflect.hasMetadata('objTest3Key', obj)) {
    console.log('objTest3Key存在')
}

使用hasMetadata传入元数据的key,查找是否存在对应元数据,第二个参数依然是存储的位置。

在类中定义

在类中有单独的元数据操作方法,可以直接在特定的位置进行存取数据。 比如在类上定义元数据:

@Reflect.metadata('decribe', '黄瓜类')
class People {

}

// 获取类上元数据
console.log(Reflect.getMetadata('decribe', People)) // "黄瓜类" 

metadata方法可以直接定义在类上,只接收元数据的keyvalue,定义的位置是固定的,在类上定义的元数据,定义的位置默认是定义在类上。 所以我们可以通过getMetadata获取在People类上的元数据。 而如果在实例方法或者实例属性上定义,则与定义装饰器效果类似,分别会定义在prototype中的对应的方法或属性上。

class People {
    @Reflect.metadata('decribe', '木星人')
    username = '瓜瓜'
    @Reflect.metadata('importinfo', '吃喝')
    eat() {}
}

// 获取方法上的元数据
console.log(Reflect.getMetadata('importinfo', People.prototype, 'eat')) // "吃喝" 
// 获取属性上的元数据
console.log(Reflect.getMetadata('decribe', People.prototype, 'username')) // "木星人" 

元数据也可以很好的适配于类之间的继承关系:

class Person extends People {
    sing() {}
}

Reflect.getMetadata('importinfo', Person.prototype, 'eat') // "吃喝"

if (Reflect.hasMetadata('importinfo', Person.prototype, 'eat')){
    console.log("hasMetadata=>Person原型上存在eat方法的importinfo元数据") // "hasMetadata=>ChinesePeople原型上存在eat方法的importinfo元数据" 
}

// hasOwnMetadata 只能获取自身的属性及方法
if (Reflect.hasOwnMetadata('importinfo', Person.prototype, 'eat')){
    console.log("hasMetadata=>ChinesePeople原型上存在eat方法的importinfo元数据")
}

元数据的数据查询方法,hasMetadata可以查询父类的方法,而hasOwnMetadata则的查询范围只包含自身类。

内置元数据Key

同样,也存在内置的一些元数据,可以获取一些内置的默认的元数据。当然也是有触发条件的:在当前装饰器修饰前提下执行下面元数据 key

design:paramtypes

a. 构造器:获得所有参数数据类型组成的数组

@Reflect.metadata('decribe', '都是人')
class People {
    constructor(addr: string, house: boolean) {}
}

Reflect.getMetadata('design:paramtypes', People)
// [ [Function: String], [Function: Boolean] ]

b. 实例方法:获取全部参数的数据类型组成的数组

class People {
    constructor(addr: string, house: boolean) {}
    @Reflect.metadata('importinfo', '吃喝')
    eat(name: string, age: number): boolean {}
}

console.log(Reflect.getMetadata('design:paramtypes', People.prototype, 'eat'))
// [ [Function: String], [Function: Number] ]

design:type

a. 实例属性:获取实例属性的数据类型

class People {
    constructor(addr: string, house: boolean) {}
  
    @Reflect.metadata('decribe', '小黄瓜')
    username: string = '瓜瓜'
}

Reflect.getMetadata('design:type', People.prototype, 'username')
// [Function: String]

b. 实例方法:获取实例方法的数据类型

class People {
    constructor(addr: string, house: boolean) {}
  
    @Reflect.metadata('importinfo', '吃喝')
    eat(name: string, age: number): boolean {}
}

Reflect.getMetadata('design:type', People.prototype, 'eat')
// [Function: Function]

design:returntype

a:获取实例方法返回值的数据类型

class People {
    constructor(addr: string, house: boolean) {}

    @Reflect.metadata('importinfo', '吃喝')
    eat(name: string, age: number): boolean {}
}

Reflect.getMetadata('design:returntype', People.prototype, 'eat')
// [Function: Boolean]

Reflect. getMetadataKeys

Reflect.getMetadataKeys是一个内置方法,用于获取指定位置所有的元数据:

class People {
    constructor(addr: string, house: boolean) {}

    @Reflect.metadata('test1', '吃喝')
    @Reflect.metadata('test2', '玩乐')
    eat(name: string, age: number): boolean {}
}

Reflect.getMetadataKeys(People.prototype, 'eat')
[LOG]: ["design:returntype", "design:paramtypes", "design:type", "test2", "test1"] 

其中也包含了默认的三个元数据key

源码

有删减和更改:

// "use strict";

// 判断避免重复执行
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  // argsnum 参数个数
  var argsnum = arguments.length;
  // targetinfo 被装饰器修饰的目标【类或属性或方法或方法参数】
  // argsnum=2 装饰器修饰的是类或者构造器参数,targetinfo=target[类名]
  // argsnum=4 装饰器修饰的是方法【第四个参数desc等于null] targetinfo=该方法的数据属性【desc = Object.getOwnPropertyDescriptor(target, key) 】
  // argsnum=3 装饰器修饰的是方法参数或者属性,targetinfo=undefined
  var targetinfo = argsnum < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc;
  // decorator保存装饰器数组元素
  var decorator;
  // 元数据信息,支持reflect-metadata元数据
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
    targetinfo = Reflect.decorate(decorators, target, key, desc);
  } else
    //  装饰器循环,倒着循环,说明同一个目标上有多个装饰器,执行顺序是倒着执行
    for (var i = decorators.length - 1; i >= 0; i--) {
      if (decorator = decorators[i]) {
        // 如果参数小于3【decorator为类装饰器或者构造器参数装饰器】执行decorator(targetinfo)直接执行decorator装饰器,并传递目标targetinfo,这里是类
        // 如果参数大于3【decorator为方法装饰器】 直接执行 decorator(target, key, targetinfo) 
        // 如果参数等于3 【decorator为方法参数装饰器或者属性装饰器】 直接执行decorator(target, key)
        // targetinfo最终为各个装饰器执行后的返回值,但如果没有返回值,直接返回第S100行的targetinfo
        targetinfo = (argsnum < 3 ? decorator(targetinfo) : argsnum > 3 ?
          decorator(target, key, targetinfo) : decorator(target, key)) || targetinfo;
      }
    }
  return argsnum > 3 && targetinfo && Object.defineProperty(target, key, targetinfo), targetinfo;
}


var __metadata = (this && this.__metadata) || function (k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
    return Reflect.metadata(k, v);
  }
};

var __param = (this && this.__param) || function (paramIndex, decorator) {
  return function (target, key) { 
    decorator(target, key, paramIndex);
  }
};

function TestFn(info) {
  console.log(`${info} 执行`);
  return function () {
    console.log(`${info} 应用`);
  };
}

let Person = class Person {
  constructor(name) { }
  handler(args) { }
};

// 装饰器执行顺序
__decorate([
  TestFn('属性'),
  __metadata("design:type", Number)
], Person.prototype, "prop", void 0);

__decorate([
  TestFn('方法'),
  __param(0, TestFn('方法参数')),
  __metadata("design:type", Function),
  __metadata("design:paramtypes", [Object]),
  __metadata("design:returntype", void 0)
], Person.prototype, "handler", null);


Person = __decorate([
  TestFn('类'),
  __param(0, TestFn('构造函数参数')),
  __metadata("design:paramtypes", [String])
], Person);

一个小Demo:使用express + 装饰器实现get请求

首先定义express中的router

import { Router } from 'express'
export const router: Router = Router();

定义app.ts文件,本文的重点不是express,直接粘贴本段代码就可以了:

import 'reflect-metadata'
import express from 'express' 
import session from 'express-session'
// 引入控制器ts文件,会自动执行HomeController文件中方法装饰器@get和类装饰器@Controller
// 因为装饰器在/router/controlldecorators 这个文件中,
// 这一执行直接导致router增加了路由完成,就是controlldecorators的第S100行代码的执行

import './controller/Food'

// 然后在引入路由器
import { router } from './utils/router'

//Express4.16+已经在express包中加入了bodyParser,可直接作为express的方法使用.
const app = express();//Creates an Express application.

// 设置session关联的cookie信息
app.use(session({
  secret: "cookecode0000",
  name: "xiaohaunggua",
  resave: false,
  saveUninitialized: true,
}))

//   Express4.16+后的 处理表单数据 url 集成到了express框架中
app.use(express.urlencoded({ extended: false }))////处理表单数据 url
app.use(router)//添加路由到express应用对象-app对象中

const server = app.listen(8080, function () {
  console.log('node服务器启动,端口8080') //服务启动完成,输出日志
})

定义get/post方法,会直接作为实例方法的装饰器使用,实现一个工厂函数,用于生成请求函数,并将请求路径以及请求方式定义为元数据:

import 'reflect-metadata'

type MethodType = "get" | "post"

// 封装方法装饰器
function reqMethodDecorator(methodType: MethodType) {
  return function (path: string): MethodDecorator {
    return (targetPrototype, methodname) => {
      // 将请求路径及请求方式挂载到实例方法上
      Reflect.defineMetadata('path', path, targetPrototype, methodname)
      Reflect.defineMetadata('methodtype', methodType, targetPrototype, methodname)
    }
  }
}

export const Get = reqMethodDecorator("get")
export const Post = reqMethodDecorator("post")

接下来实现FoodController类,主要专注于定义请求路径的处理函数:

import { Request, Response } from 'express'

// 定义根路径
@Controller("/")
class FoodController {
  @Get("/showFood/:foodname/:price")
  showFood(req: Request, res: Response): void {
    res.setHeader("Content-Type", "text/html; charset=utf-8")
    let foodname = req.params.foodname
    let price = req.params.price
    res.write(`food:${foodname}`);
    res.write(`food:${price}`);
    res.write("very good");
    res.write("nice")

    res.end();
  }
}

当请求/showFood/:foodname/:price这个路径时,会将拼接的参数返回。

最后实现类装饰器,类装饰器的执行顺序优先级最低,也就是最后执行,这也就保证了我们可以在类装饰中获取到实例方法装饰器中定义的元数据,然后通过实例方法名在prototype中获取函数绑定在router对象中,类装饰器中也可以拼接定义根域名。

import { router } from '../utils/router'
import { RequestHandler } from 'express'

export function Controller(rootPath: string): ClassDecorator {
  return (target) => {
    for(let methodName in target.prototype) {
      // 根据实例方法名获取实例方法元数据
      let routerPath = Reflect.getMetadata('path', target.prototype, methodName)
      let reqName: MethodType = Reflect.getMetadata('methodtype', target.prototype, methodName)
      // 获取对应的实例方法
      const targetMethodfunc: RequestHandler = target.prototype[methodName];
      // 挂载到router对象
      if(routerPath && reqName) {
        router[reqName](routerPath, targetMethodfunc)
      }
    }
  }
}

深入浅出Typescript装饰器与Reflect元数据

完成!

写在最后 ⛳

未来可能会更新typescriptreact基础知识系列,希望能一直坚持下去,期待多多点赞🤗🤗,一起进步!🥳🥳

转载自:https://juejin.cn/post/7212996764387377209
评论
请登录