深入理解Typescript系列-装饰器

1,022 阅读3分钟

这是我参与 8 月更文挑战的第 17 天,活动详情查看: 8月更文挑战

前言

本章内容我们主要来聊聊TS中的装饰器,装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

为什么要使用装饰器?

装饰器也是一种设计模式。在日常开发中,我们有时总有这样的诉求:在函数执行前,先进行一个类型校验、数据过滤、调用次数的限制等操作,对于函数,属于业务代码,直接改造不适合,那么是否可以加入一个中间层,来帮忙处理一些比较特殊的逻辑呢?那么,装饰器就是首选。

初探TS装饰器

我们先来看下装饰器的语法:

function helloWord(target: any) {
    console.log('hello Word!');
}

@helloWord
class HelloWordClass {

}

在执行后,会立即输出hello Word!,装饰器函数是一种自执行函数。但是,这种自执行是在编译期间完成的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。

方法装饰器

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

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

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 成员的名字。
  3. 成员的属性描述符。
class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }

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

访问器装饰器

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

class Point {
    private _x: number;
    private _y: number;
    constructor(x: number, y: number) {
        this._x = x;
        this._y = y;
    }

    @configurable(false)
    get x() { return this._x; }

    @configurable(false)
    get y() { return this._y; }
}

属性装饰器

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

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);
    }
}

bable配置

若要启用实验性的装饰器特性,你必须在命令行或tsconfig.json里启用experimentalDecorators编译器选项:

tsconfig.json:

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

小结

基于我们的理论和实践,我们可以得出以下结论:

  • 对于多个参数装饰器的执行顺序:从最后一个参数开始,但是类装饰器总是最后执行。

  • 方法和方法参数中参数装饰器先执行。

  • 方法和属性装饰器,谁在前面谁先执行。因为参数属于方法一部分,所以参数会一直紧紧挨着方法执行。