TS之装饰器Decorators

158 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情

前言

随着TypeScriptES6中类的引入,现在存在某些场景需要额外的功能来支持注释或修改类和类成员。装饰器提供了一种为类声明和成员添加注释和元编程语法的方法。装饰器是JavaScript的第二阶段建议,并作为TypeScript的一个实验性功能提供。

一. 安装

要启用对装饰器的实验性支持,必须在命令行或在tsconfig.json中启用experimentalDecorators编译器选项。

{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
 }
}

或者命令行输入:

tsc --target ES5 --experimentalDecorators

二. 使用方式

1. 基础使用

装饰器是一种特殊的声明,可以附加到类声明、方法、访问器、属性或参数上。使用@expression的形式,其中expression必须评估为一个函数,该函数将在运行时被调用,并带有关于被装饰的声明的信息。

//装饰器sealed
function sealed(target) {

// do something with 'target' ...

}

2.装饰器工厂

如果我们想自定义装饰器如何应用于声明,我们可以写一个装饰器工厂。装饰器工厂只是一个函数,它返回将在运行时被装饰器调用的表达式。

function color(value: string) {

// this is the decorator factory, it sets up

// the returned decorator function

return function (target) {

// this is the decorator

// do something with 'target' and 'value'...

};

}

多个装饰器可以应用于一个声明,可以在一行或者多行中使用。当一个声明上的有多个装饰器时,会执行以下步骤:

  1. 每个装饰器的表达式都从上到下进行评估。
  2. 将结果作为函数从下往上调用。

举例:

function first() {

console.log("first(): factory evaluated");

return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {

console.log("first(): called");

};

}

 

function second() {

console.log("second(): factory evaluated");

return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {

console.log("second(): called");

};

}

 

class ExampleClass {

@first()

@second()

method() {}

}

执行顺序如下:

image.png

3. 类装饰器

类装饰器就在类声明之前被声明。类装饰器被应用于类的构造函数,可以用来观察、修改或替换类定义。类装饰器不能在声明文件中使用,也不能在任何其他环境下使用(比如在声明类上)。

应用于类内各种声明的装饰器的,有一个明确的顺序:

  1. 对于每个实例成员,首先是参数装饰器,然后是方法、访问器或属性装饰器。
  2. 对于每个静态成员,首先是参数装饰器,然后是方法、访问器或属性装饰器。
  3. 参数装饰器被应用于构造函数。
  4. 类装饰器适用于类。

类装饰器的表达式将在运行时作为一个函数被调用,被装饰的类的构造函数是其唯一的参数。
如果类装饰器返回一个值,它将用提供的构造函数替换类声明。

举例:

//装饰器sealed
function sealed(constructor: Function) {

Object.seal(constructor);

Object.seal(constructor.prototype);

}



@sealed

class BugReport {

type = "report";

title: string;


constructor(t: string) {

this.title = t;

}

}

@sealed被执行时,它将同时封闭构造函数和它的原型,因此将阻止在运行时通过访问BugReport.prototype或通过定义BugReport本身的属性来向该类添加或删除任何进一步的功能(注意ES2015类实际上只是基于原型的构造函数的语法糖)。这个装饰器并不能阻止类对BugReport进行子类化。

接下来我们有一个如何覆盖构造函数以设置新的默认值的例子:

// @errors: 2339
// @experimentalDecorators
function reportableClassDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    reportingURL = "http://www...";
  };
}

@reportableClassDecorator
class BugReport {
  type = "report";
  title: string;

  constructor(t: string) {
    this.title = t;
  }
}

const bug = new BugReport("Needs dark mode");
console.log(bug.title); // Prints "Needs dark mode"
console.log(bug.type); // Prints "report"

// Note that the decorator _does not_ change the TypeScript type
// and so the new property `reportingURL` is not known
// to the type system:
bug.reportingURL; //error

image.png

4. 方法装饰器(ES5之后)

方法装饰器就在方法声明之前被声明。该装饰器被应用于方法的属性描述符,可以用来观察、修改或替换方法定义。一个方法装饰器不能在声明文件中使用,不能在重载上使用,也不能在任何其他的环境中使用(比如在声明类中)。

方法装饰器的表达式将在运行时作为一个函数被调用,有以下三个参数:

  1. 静态成员的类的构造函数,或者实例成员的类的原型。
  2. 成员的名称。
  3. 该成员的属性描述符。

注意:如果方法装饰器返回一个值,它将被用作方法的属性描述符。

function enumerable(value: boolean) {

return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {

descriptor.enumerable = value;

};

}

当@enumerable(false)装饰器被调用时,它修改了属性描述符的enumerable属性。

class Greeter {

greeting: string;

constructor(message: string) {

this.greeting = message;

}

 

@enumerable(false)

greet() {

return "Hello, " + this.greeting;

}

}

结语

本文章参考官方文档,后续还有一些访问装饰器,属性装饰器,大家可自行对照文档学习。 typescript官方文档