TypeScript装饰器

105 阅读4分钟

一、简介

  1. 装饰器的本质其实就是一种特殊的函数,它可以对:类、属性、方法、参数进行扩展,同时能让代码更简洁。
  2. 装饰器自2015年在ECMAScript-6中被提出到现在,已将近10年。
  3. 截止目前,装饰器依然是实验性特性,需要开发者手动调整配置,来开启装饰器支持。
  4. 装饰器有5种
    • 类装饰器
    • 属性装饰器
    • 方法装饰器
    • 访问器装饰器
    • 参数装饰器

备注:虽然TypeScript5.0中可以直接使用 类装饰器,但是为了确保其他装饰器可用,现阶段使用时,仍然建议使用 experimentalDecorators 配置来开启装饰器支持,而且不排除在未来版本中,官方会进一步调整装饰器的相关语法!

二、类装饰器

1、基本语法

类装饰器是一个应用在类声明上的函数,可以为类添加额外的功能,或添加额外的逻辑。

image.png

2、举例说明

写一个类装饰器,可以输出类的toString完整内容

image.png

3、关于返回值

  • 类装饰器返回值:若类装饰器返回一个新的类,那这个新类将替换掉被装饰的类。
  • 类装饰器返回值:若类装饰器无返回值或返回 undefined ,那被装饰的类不会被替换。

image.png

4、关于构造类型

在TypeScript中,Function 类型所表示的范围十分广泛,包括:普通函数、箭头函数、方法等等。 但并非 Function 类型的函数都可以被 new 关键字实例化,例如箭头函数是不能被实例化的,那么TypeScript 中该如何声明一个构造类型呢?有以下两种方式:

仅声明构造类型 image.png

声明构造类型+指定静态属性 image.png

5、替换被装饰的类

对于高级一些的装饰器,不仅仅是覆盖一个原型上的方法,还要有更多功能,例如添加新的方法和状态。

需求:设计一个LogTime装饰器,可以给实例添加一个属性,用于记录实例对象的创建时间,再添加一个方法用于读取创建时间。

image.png

三、装饰器工厂

装饰器工厂是一个返回装饰器函数的函数,可以为装饰器添加参数,可以更灵活地控制装饰器的行为。

需求:定义一个 LogInfo 类装饰器工厂,实现 Person 实例可以调用到 introduce 方法,且 introduce 中输出内容的次数,由 LogInfo 接收的参数决定。

image.png

四、装饰器组合

装饰器可以组合使用,执行顺序为:先【由上到下】的执行所有的装饰器工厂,依次获取到装饰器,然后再【由下到上】执行所有的装饰器。

装饰器组合-执行顺序 image.png

装饰器组合-应用


function CustomToString(target: Function) {
  target.prototype.toString = function () {
    return JSON.stringify(this); //this target的实例对象
  };
  Object.seal(target); //密封对象,禁止添加新属性
}

function LogInfo(n: number) {
  return function (target: Function) {
    //装饰器
    target.prototype.introduce = function () {
      for (let i = 0; i < n; i++) {
        console.log(`我是${this.name},我今年${this.age}岁了`);
      }
    };
  };
}

type Constructor = new (...args: any[]) => {};
function LogTime<T extends Constructor>(target: T) {
  return class extends target {
    createdTime: Date;
    constructor(...args: any[]) {
      super(...args);
      this.createdTime = new Date();
    }
    getTime() {
      console.log(this.createdTime);
    }
  };
}

@LogTime
@LogInfo(3)
@CustomToString
class Person {
  constructor(public name: string, public age: number) {}
  speak() {
    console.log(this.name, this.age);
  }
}

interface Person {
  getTime(): void;
  introduce(): void;
}

const p = new Person("孙悟空", 18);
p.speak();
console.log(p.toString());
p.getTime();
p.introduce();

五、属性装饰器

1、基本语法

image.png

2、关于属性遮蔽

如下代码中,当构造器中的 this.age = age 试图在实例上赋值时,实际上是调用了原型上 age 属性的 set 方法

image.png

3、应用举例

需求:定义一个State属性装饰器,来监视属性的修改。

image.png

六、方法装饰器

1、基本语法

image.png

2、应用举例

需求:

  • 定义一个Logger方法装饰器,用于在方法执行前和执行后,均追加一些额外逻辑
  • 定义一个Validate方法装饰器,用于验证数据
function Logger(
  target: object,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  //缓存原始方法
  const originnal = descriptor.value;
  //替换原始方法
  descriptor.value = function (...args: any[]) {
    console.log(propertyKey + "开始执行");
    const result = originnal.call(this, ...args);
    console.log(propertyKey + "执行完毕");
    return result;
  };
}

function Validate(maxValue: number) {
  return function (
    target: object,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originnal = descriptor.value;
    descriptor.value = function (...args: any[]) {
      if (args[0] > maxValue) {
        throw new Error("年龄非法");
      }
      //如果所有参数都符合要求,就执行原始方法
      return originnal.call(this, ...args);
    };
  };
}

class Person {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  @Logger
  speak() {
    console.log(this.name, this.age);
  }
  @Validate(120)
  static isAdult(age: number) {
    return age >= 18;
  }
}

const p1 = new Person("孙悟空", 300);
p1.speak();
console.log(Person.isAdult(20));


七、访问器装饰器

1、基本语法

image.png

2、应用举例

function RangeValidate(min: number, max: number) {
  return function (
    target: object,
    propertyKey: string,
    discriptor: PropertyDescriptor
  ) {
    //保存原始set方法
    const originnalSetter = discriptor.set;
    //重写
    discriptor.set = function (value: number) {
      if (value < min || value > max) {
        throw new Error("温度值非法");
      }
      //如果值合法,就执行原始set方法
      if (originnalSetter) {
        originnalSetter.call(this, value);
      }
    };
  };
}

class Weather {
  private _temp: number;
  constructor(_temp: number) {
    this._temp = _temp;
  }
  get temp() {
    return this._temp;
  }
  @RangeValidate(-50, 50) //范围验证
  set temp(temp: number) {
    this._temp = temp;
  }
}

const w = new Weather(28);
console.log(w.temp); //28
w.temp = 30;
console.log(w.temp); //30
w.temp = 60;
console.log(w.temp); //温度值非法

八、参数装饰器

1、基本语法

image.png