TypeScript 装饰器

装饰器是一个附加到类或类的某个部分(例如方法或属性)的函数,用于修改或添加行为。

您可以通过在类或类成员的上方放置 `@` 符号,后跟装饰器函数名来装饰它。

这是一个快速的装饰器示例。您可以在本教程的其余部分了解更多信息。

示例

// Decorator function that logs when a class is created
function logger(value: Function, context: ClassDecoratorContext) {

    // Print the name of the class being decorated
    console.log(`Creating class - ${context.name}`)
}

// Here, @logger is a decorator that calls the logger() function
@logger
class Person {
    constructor(public name: string) {}
}

// Output: Creating class - Person

在此,`logger()` 是一个修改整个类的装饰器函数。在类声明前使用 `@logger` 可确保装饰器在类定义时运行,而不是在实例化时运行。


启用装饰器

要使用装饰器,您需要在 TypeScript 配置文件(即 `tsconfig.json`)中添加以下设置:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "useDefineForClassFields": true
  }
}

装饰器类型

TypeScript 中可用的主要装饰器类型是:

装饰器类型 应用于 用例
整个类 日志记录、密封
属性 类字段 跟踪访问、验证
方法 类方法 计时、日志记录调用
访问器 Getter/setter 隐藏、修改行为

提示:如果您对装饰器仍然感到困惑,只需将其视为一个“贴纸”,您将其“贴”在类或其成员上以提供额外信息或功能。


1. 类装饰器

我们使用类装饰器来修改类。

  • 作用:应用于整个类。
  • 接收:类的构造函数。
  • 用例:可以修改或密封类。

语法

function decoratorName(value: Function, context: ClassDecoratorContext) {
    // Decorator code
}

类装饰器的参数含义如下:

参数 类型 描述
value (值) 函数 指代正在被装饰的类构造函数
context ClassDecoratorContext 提供有关被装饰类的元数据(例如类的 `name`、其 `kind` 以及 `addInitializer`)。

理解装饰器

让我们通过前面的例子来尝试理解装饰器的基本工作原理。

// Create a decorator function
// that logs when a class is created
function logger(value: Function, context: ClassDecoratorContext) {

    // Print the name of the class being decorated
    console.log(`Decorated Entity: ${context.name}`)

    // Print the kind of the decorated entity
    console.log(`Entity Kind: ${context.kind}`)

    // Print the raw value of the constructor function
    console.log("Entity Constructor:")
    console.log(value)
}

// Use the decorator on a class
@logger
class Person {
    constructor(public name: string) {}
}

输出

Decorated Entity: Person
Entity Kind: class
Entity Constructor:
[class Person]

在这里,装饰器函数 `logger()` 是一个修改类的类装饰器

在装饰器内部:

  • `context.name` - 提供我们将要装饰的实体(类)的名称。
  • `context.kind` - 提供我们将要装饰的实体的类型。
  • `console.log(value)` - 打印构造函数的原始值。

最后,我们通过在类声明前使用 `@decoratorName` 语法来装饰一个类。

// Use the decorator on a class
@logger
class Person {
    constructor(public name: string) {}
}

请注意,我们没有创建类的实例。这表明装饰器在类定义时执行,而不是在实例化时执行。

提示:您可以使用泛型来提高类型安全性。例如:

function logger<T extends Function>(value: T, context: ClassDecoratorContext) {
    // Decorator code
}

示例 1:密封类的装饰器

现在,让我们创建一个装饰器来密封一个类,即防止修改其结构,例如添加或删除属性。

function sealed<T extends Function>(value: T, context: ClassDecoratorContext) {
    Object.seal(value);
    Object.seal(value.prototype);
    console.log("Class sealed!");
}

@sealed
class Vehicle {
    wheels: number = 4;
}

// Try to add a new property
// Will fail silently or throw error in strict mode
(Vehicle as any).newProp = "test";
console.log((Vehicle as any).newProp);

输出

Class sealed!
undefined

2. 属性装饰器

属性装饰器用于修改或扩展类字段的行为。

  • 作用:应用于类字段。
  • 接收:通过 `ClassFieldDecoratorContext` 提供的元数据。
  • 用例:可以记录读写或强制执行规则。

语法

function decoratorName(value: undefined, context: ClassFieldDecoratorContext) {
    // Decorator code
}

目前可以忽略 `value` 参数 — 它始终是 `undefined`,因为装饰器在类定义时应用,在创建任何实例之前。


示例 2:TypeScript 属性装饰器

function logAccess(value: undefined, context: ClassFieldDecoratorContext) {
    let backingField = Symbol();

    context.addInitializer(function () {
        Object.defineProperty(this, context.name, {
            get() {
                console.log(`Getting property: ${String(context.name)}`);
                return this[backingField];
            },
            set(newValue: any) {
                console.log(`Setting property: ${String(context.name)} to ${newValue}`);
                this[backingField] = newValue;
            },
            enumerable: true,
            configurable: true
        });
    });
}

class Product {
    @logAccess
    name: string;

    constructor(name: string) {
        this.name = name;
    }
}

const laptop = new Product("Laptop");
console.log(laptop.name); // Logs access
laptop.name = "Gaming Laptop"; // Logs change

输出

Setting property: name to Laptop
Getting property: name
Laptop
Setting property: name to Gaming Laptop

在这里,`logAccess()` 是一个属性装饰器,它记录对类属性的访问并设置该属性的值。

在此装饰器内部:

  • `backingField` 是一个私有的内部键,用于存储被装饰属性(`name`)的实际值,而不会干扰公共接口。
  • `context.addInitializer()` 允许您在实例初始化期间使用自定义的 getter 和 setter 来设置属性。

3. 方法装饰器

我们使用方法装饰器来装饰类的方法。

  • 作用:应用于方法。
  • 接收:通过 `ClassMethodDecoratorContext` 提供的元数据。
  • 用例:在方法运行之前/之后添加逻辑。

语法

function decoratorName(target: any, context: ClassMethodDecoratorContext) {
    // Decorator code
}

在这里,`target` 是被装饰的原始方法函数。


示例 3:TypeScript 方法装饰器

function measureTime(target: any, context: ClassMethodDecoratorContext) {
    const originalMethod = target;

    return function (this: any, ...args: any[]) {
        console.time("Execution");
        const result = originalMethod.apply(this, args);
        console.timeEnd("Execution");
        return result;
    };
}

class Calculator {
    @measureTime
    add(a: number, b: number): number {
        // Simulate delay
        for (let i = 0; i < 1000000; i++) {}
        return a + b;
    }
}

const calc = new Calculator();
calc.add(5, 3);

输出

Execution: 1.648ms

此程序使用一个名为 `measureTime()` 的装饰器来测量 `add()` 方法的运行时间。

该装饰器包装了 `add()` 方法,并使用 `console.time()` 和 `console.timeEnd()` 记录执行时间。

工作原理

当调用 `calc.add(5, 3)` 时,装饰器会:

  1. 启动一个计时器。
  2. 运行原始的 `add()` 方法。
  3. 停止计时器。
  4. 记录所需时间。

`add()` 中的 `for` 循环会增加一个短暂的延迟,以便我们能够看到计时。


4. 访问器装饰器

访问器装饰器用于修改类中getter 和 setter 的行为。

  • 作用:应用于 `get` 或 `set` 访问器。
  • 接收:`ClassGetterDecoratorContext` 或 `ClassSetterDecoratorContext`。
  • 用例:控制访问器的可见性或行为。

语法

function decoratorName(
    value: any,
    context: ClassGetterDecoratorContext | ClassSetterDecoratorContext
) {
    // Decorator code
}

在这里,`value` 是实际的 getter 或 setter 函数。由于我们为 `context` 使用了联合类型,该装饰器可以同时用于 getter 和 setter。


示例 4:TypeScript 访问器装饰器

function isHidden(
    value: any,
    context: ClassGetterDecoratorContext | ClassSetterDecoratorContext
) {
    context.addInitializer(function () {
        Object.defineProperty(this, context.name, {
            enumerable: false
        });
    });
    return value;
}

class UserProfile {
    private _age: number;

    constructor(age: number) {
        this._age = age;
    }

    @isHidden
    get age(): number {
        return this._age;
    }

    set age(value: number) {
        this._age = value;
    }
}

const user = new UserProfile(30);

// Using the getter gives 'undefined' because it is hidden
console.log(user.age);

// Verifying that '_age' property still exists
console.log(Object.keys(user));

输出

undefined
[ '_age' ]

在这里,`isHidden()` 装饰器将访问器从对象枚举中隐藏。在本例中,我们隐藏了 `get age()` 的枚举。

因此,您将无法查看 `_age` 属性,因为其 getter 函数已被隐藏。

你觉得这篇文章有帮助吗?

我们的高级学习平台,凭借十多年的经验和数千条反馈创建。

以前所未有的方式学习和提高您的编程技能。

试用 Programiz PRO
  • 交互式课程
  • 证书
  • AI 帮助
  • 2000+ 挑战