”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 理解 TypeScript 中的装饰器:第一原理方法

理解 TypeScript 中的装饰器:第一原理方法

发布于2024-11-01
浏览:772

Understanding Decorators in TypeScript: A First-Principles Approach

TypeScript 中的装饰器提供了一种强大的机制来修改类、方法、属性和参数的行为。虽然它们看起来像是一种现代的便利,但装饰器植根于面向对象编程中成熟的装饰器模式。通过抽象日志记录、验证或访问控制等常见功能,装饰器允许开发人员编写更清晰、更易于维护的代码。

在本文中,我们将从首要原则探索装饰器,分解其核心功能,并从头开始实现它们。在此过程中,我们将了解一些实际应用程序,这些应用程序展示了装饰器在日常 TypeScript 开发中的实用性。

什么是装饰器?

在 TypeScript 中,装饰器只是一个可以附加到类、方法、属性或参数的函数。此函数在设计时执行,使您能够在代码运行之前更改代码的行为或结构。装饰器支持元编程,允许我们在不修改原始逻辑的情况下添加额外的功能。

让我们从一个方法装饰器的简单示例开始,该示例记录调用方法的时间:

function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with arguments: ${args}`);
    return originalMethod.apply(this, args);
  };

  return descriptor;
}

class Example {
  @log
  greet(name: string) {
    return `Hello, ${name}`;
  }
}

const example = new Example();
example.greet('John');

这里,日志装饰器包装了greet方法,在执行之前记录其调用和参数。此模式对于将日志记录等横切关注点与核心逻辑分离非常有用。

装饰器如何工作

TypeScript 中的装饰器是接受与它们所装饰的项目相关的元数据的函数。基于这些元数据(如类原型、方法名称或属性描述符),装饰器可以修改行为甚至替换被装饰的对象。

装饰器的类型

装饰器可以应用于各种目标,每个目标都有不同的目的:

  • Class Decorators :接收类构造函数的函数。
function classDecorator(constructor: Function) {
  // Modify or extend the class constructor or prototype
}
  • 方法装饰器:接收目标对象、方法名称和方法描述符的函数。
function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  // Modify the method's descriptor
}
  • Property Decorators :接收目标对象和属性名称的函数。
function propertyDecorator(target: any, propertyKey: string) {
  // Modify the behavior of the property
}
  • 参数装饰器 :接收目标、方法名称和参数索引的函数。
function parameterDecorator(target: any, propertyKey: string, parameterIndex: number) {
  // Modify or inspect the method's parameter
}

将参数传递给装饰器

装饰器最强大的功能之一是它们接受参数的能力,允许您自定义它们的行为。例如,让我们创建一个方法装饰器,它根据参数有条件地记录方法调用。

function logConditionally(shouldLog: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = function (...args: any[]) {
      if (shouldLog) {
        console.log(`Calling ${propertyKey} with arguments: ${args}`);
      }
      return originalMethod.apply(this, args);
    };

    return descriptor;
  };
}

class Example {
  @logConditionally(true)
  greet(name: string) {
    return `Hello, ${name}`;
  }
}

const example = new Example();
example.greet('TypeScript Developer');

通过将 true 传递给 logConditionally 装饰器,我们确保该方法记录其执行。如果我们传递 false,则跳过日志记录。这种灵活性是使装饰器具有多功能性和可重用性的关键。

装饰器的实际应用

装饰器在许多库和框架中都有实际用途。以下是一些值得注意的示例,说明了装饰器如何简化复杂的功能:

  • 类验证器中的验证:在数据驱动的应用程序中,验证至关重要。 class-validator 包使用装饰器来简化验证 TypeScript 类中字段的过程。
import { IsEmail, IsNotEmpty } from 'class-validator';

class User {
  @IsNotEmpty()
  name: string;

  @IsEmail()
  email: string;
}

在此示例中,@IsEmail 和 @IsNotEmpty 装饰器确保电子邮件字段是有效的电子邮件地址并且名称字段不为空。这些装饰器消除了手动验证逻辑的需要,从而节省了时间。

  • 使用 TypeORM 进行对象关系映射:装饰器广泛用于 TypeORM 等 ORM 框架中,用于将 TypeScript 类映射到数据库表。此映射是使用装饰器以声明方式完成的。
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  email: string;
}

这里,@Entity、@Column和@PrimaryGeneeratedColumn定义了User表的结构。这些装饰器抽象了 SQL 表创建的复杂性,使代码更具可读性和可维护性。

  • Angular 依赖注入:在 Angular 中,装饰器在管理服务和组件方面发挥着关键作用。 @Injectable 装饰器将一个类标记为可以注入其他组件或服务的服务。
@Injectable({
  providedIn: 'root',
})
class UserService {
  constructor(private http: HttpClient) {}
}

本例中的 @Injectable 装饰器向 Angular 的依赖注入系统发出信号,表明 UserService 应在全局范围内提供。这允许跨应用程序无缝集成服务。

实现你自己的装饰器:分解

装饰器的核心就是函数。我们来分解一下从头开始创建装饰器的过程:

类装饰器

类装饰器接收类的构造函数,可以用来修改类原型,甚至替换构造函数。

function AddTimestamp(constructor: Function) {
  constructor.prototype.timestamp = new Date();
}

@AddTimestamp
class MyClass {
  id: number;
  constructor(id: number) {
    this.id = id;
  }
}

const instance = new MyClass(1);
console.log(instance.timestamp);  // Outputs the current timestamp

方法装饰器

方法装饰器修改方法描述符,允许您更改方法本身的行为。

function logExecutionTime(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    const start = performance.now();
    const result = originalMethod.apply(this, args);
    const end = performance.now();
    console.log(`${propertyKey} executed in ${end - start}ms`);
    return result;
  };

  return descriptor;
}

class Service {
  @logExecutionTime
  execute() {
    // Simulate work
    for (let i = 0; i 



物业装修师

属性装饰器允许您拦截属性访问和修改,这对于跟踪更改非常有用。

function trackChanges(target: any, propertyKey: string) {
  let value = target[propertyKey];

  const getter = () => value;
  const setter = (newValue: any) => {
    console.log(`${propertyKey} changed from ${value} to ${newValue}`);
    value = newValue;
  };

  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
  });
}

class Product {
  @trackChanges
  price: number;

  constructor(price: number) {
    this.price = price;
  }
}

const product = new Product(100);
product.price = 200;  // Logs the change

结论

TypeScript 中的装饰器允许您以干净、声明的方式抽象和重用功能。无论您使用验证、ORM 还是依赖注入,装饰器都有助于减少样板文件并保持代码模块化和可维护性。从基本原理出发了解它们的工作原理可以更轻松地充分利用它们的潜力并根据您的应用定制定制解决方案。

通过更深入地了解装饰器的结构和实际应用,您现在已经了解了它们如何简化复杂的编码任务并简化各个领域的代码。

版本声明 本文转载于:https://dev.to/curious_tinkerer/understanding-decorators-in-typescript-a-first-principles-approach-2n3h?1如有侵犯,请联系[email protected]删除
最新教程 更多>

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3