TypeScript入门之装饰器

593 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情

简介

装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、属性或参数上,可以修改类的行为。

使用@装饰器需要把 tsconfig.jsonexperimentalDecorators 字段设置为 true

装饰器类型

TypeScript 里,主要有类装饰器、方法装饰器、属性装饰器、参数装饰器。

类装饰器

类装饰器定义在类上面。类装饰器运行是在类初始化的时候。

类装饰器的参数只有一个,参数是类本身。

比如,我们声明一个函数 addAge 去给 Class 的属性 age 添加年龄.

function addAge(constructor: Function) {
  constructor.prototype.age = 18;
}

@addAge
class Person{
  name: string;
  age!: number;
  constructor() {
    this.name = 'randy';
  }
}

let person = new Person();

console.log(person.age); // 18

所以这段代码实际上基本等同于:

Person = addAge(function Person() { ... });

constructor.prototype.age 就是在类原型上添加了age属性并赋值18。这样我们通过类Person实例化出来的每个对象都会有age属性,并且值为18

属性/方法装饰器

实际上一个Class的属性/方法也可以被装饰,我们分别给 Person 类加上 say 和 run 方法。

属性/方法装饰器运行是在类初始化的时候。

// 声明装饰器修饰方法
function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
   console.log(target);
   console.log("prop " + propertyKey);
   console.log("desc " + JSON.stringify(descriptor) + "\n\n");
   descriptor.writable = false;
};

// 声明装饰器修饰属性
function prop(target: any, key: string): any {
  console.log(target);
  console.log("prop " + key);
  const descriptor: PropertyDescriptor = {
    writable: true,
    configurable: true,
    enumerable: true,
    value: "jack",
  };
  
  return descriptor;
}

// 类
class Person{
  @prop
  name: string;
  constructor() {
    this.name = 'randy';
  }

  @method
  say(){
    return 'instance method';
  }

  @method
  static run(){
    return 'static method';
  }
}

普通函数和静态函数参数是有区别的。

  1. 普通函数的装饰器接收的参数第一个是类的原型第二个是方法名第三个是修饰符。

  2. static 函数的装饰器接收的参数第一个是类本身第二个是方法名第三个是修饰符。

属性的装饰器接收的参数第一个是类的原型第二个是属性名。

属性的装饰器可以返回属性描述符来描述该属性。

如果还不清楚Object相关API的可以看看笔者前面写的JS Object API详解这篇文章。

访问器属性getter或者setter同样可以用属性装饰器修饰,这里笔者就不再细说了。

参数装饰器

参数装饰器,顾名思义是用于修饰参数的装饰器。

function logParameter(target: Object, propertyKey: string, index: number) {
  console.log(target, propertyKey, index);
}

class Person {
  greet(@logParameter message: string, @logParameter name: string): string {
    return `${message} ${name}`;
  }
}
const p = new Person();
p.greet('hello', 'randy');

// Person { greet: [Function] } greet 1
// Person { greet: [Function] } greet 0

我们看到参数装饰器需要三个参数 targetpropertyKeyindex

  • target —— 类的原型,也就是Person.prototype
  • propertyKey —— 参数的名称,上例中指的就是 greet
  • index —— 参数数组中的位置,比如上例中参数 name 的位置是 1, message 的位置为 0

多个装饰器

一个类、属性、方法可以同时被多个装饰器修饰。

多个装饰器可以同时应用到一个声明上,就像下面的示例:

  • 书写在同一行上:
@f @g x
  • 书写在多行上:
@f
@g
x

TypeScript 里,当多个装饰器应用在一个声明上时执行顺序从下往上执行。

上面的例子会先运行@g然后运行@f

装饰器执行顺序

当我们定义了属性装饰器、普通方法装饰器、参数装饰器、静态方法装饰器、类装饰器的时候这些装饰器的执行顺序是怎么样的呢?

装饰器的执行顺序总的来说先执行class内部(属性、方法、参数)装饰器再执行类装饰器。这个顺序是固定的。

class内部(属性、方法、参数)装饰器的执行顺序是不固定的,依赖你在class内部的书写顺序。书写越靠前,越先执行。

当参数装饰器和方法装饰器同时定义在同一个方法上的时候,先执行参数装饰器再执行方法装饰器。

系列文章

TypeScript入门之环境搭建

TypeScript入门之数据类型

TypeScript入门之函数

TypeScript入门之接口

TypeScript入门之类

TypeScript入门之类型推断、类型断言、双重断言、非空断言、确定赋值断言、类型守卫、类型别名

TypeScript入门之泛型

TypeScript入门之装饰器

TypeScript入门之模块与命名空间

TypeScript入门之申明文件

TypeScript入门之常用内置工具类型

TypeScript入门之配置文件

后记

感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!