TypeScript装饰器:从入门到精通

发布于:2025-05-14 ⋅ 阅读:(21) ⋅ 点赞:(0)

TypeScript装饰器:从入门到精通

什么是装饰器?

装饰器(Decorator)是TypeScript中一个非常酷的特性,它允许我们在不修改原有代码的情况下,给类、方法、属性等添加额外的功能。想象一下装饰器就像给你的代码"穿衣服",可以一层层地叠加功能,而不会破坏原有的结构。

在ES2016中,装饰器还只是一个提案,但TypeScript已经提前实现了这个功能。要使用装饰器,需要在tsconfig.json中开启experimentalDecorators选项。

装饰器基础

类装饰器

类装饰器是最简单的一种装饰器,它接收一个构造函数作为参数。让我们看个例子:

function logClass(target: Function) {
  console.log(`${target.name} 被装饰了`);
}

@logClass
class MyClass {
  constructor() {
    console.log('创建MyClass实例');
  }
}

const myClass = new MyClass();
// 输出:
// 类 MyClass 被装饰了
// 创建MyClass实例

方法装饰器

方法装饰器可以拦截方法的调用,非常适合用于日志记录、权限验证等场景:

function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    console.log(`调用方法 ${propertyKey},参数: ${JSON.stringify(args)}`);
    const result = originalMethod.apply(this, args);
    console.log(`方法 ${propertyKey} 返回: ${result}`);
    return result;
  };
  
  return descriptor;
}

class Calculator {
  @logMethod
  add(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(2, 3);
// 输出:
// 调用方法 add,参数: [2,3]
// 方法 add 返回: 5

装饰器工厂

有时候我们希望装饰器能接收参数,这时候就需要使用装饰器工厂:

function logWithPrefix(prefix: string) {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    
    descriptor.value = function(...args: any[]) {
      console.log(`[${prefix}] 调用方法 ${propertyKey}`);
      return originalMethod.apply(this, args);
    };
    
    return descriptor;
  };
}

class Logger {
  @logWithPrefix('DEBUG')
  log(message: string) {
    console.log(message);
  }
}

const logger = new Logger();
logger.log('这是一条日志');
// 输出:
// [DEBUG] 调用方法 log
// 这是一条日志

装饰器的执行顺序

当多个装饰器应用于同一个声明时,它们的执行顺序很重要:

  1. 参数装饰器,然后是方法、访问器或属性装饰器
  2. 装饰器从最靠近声明的装饰器开始执行
  3. 类装饰器最后执行
function first() {
  console.log('first() 工厂函数');
  return function(target: any) {
    console.log('first() 装饰器');
  };
}

function second() {
  console.log('second() 工厂函数');
  return function(target: any) {
    console.log('second() 装饰器');
  };
}

@first()
@second()
class ExampleClass {}

// 输出:
// first() 工厂函数
// second() 工厂函数
// second() 装饰器
// first() 装饰器

装饰器的实际应用

1. 自动绑定this

在React类组件中,我们经常需要绑定方法的this指向,装饰器可以帮我们自动完成:

function autobind(_: any, _2: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  const adjDescriptor: PropertyDescriptor = {
    configurable: true,
    enumerable: false,
    get() {
      const boundFn = originalMethod.bind(this);
      return boundFn;
    }
  };
  return adjDescriptor;
}

class Button {
  @autobind
  onClick() {
    console.log('按钮被点击', this);
  }
}

const button = new Button();
document.addEventListener('click', button.onClick);

2. 表单验证

装饰器可以很方便地实现表单验证逻辑:

function validate(min: number, max: number) {
  return function(target: any, propertyKey: string) {
    let value: number;
    
    const getter = function() {
      return value;
    };
    
    const setter = function(newVal: number) {
      if (newVal < min || newVal > max) {
        throw new Error(`值必须在 ${min}${max} 之间`);
      }
      value = newVal;
    };
    
    Object.defineProperty(target, propertyKey, {
      get: getter,
      set: setter
    });
  };
}

class User {
  @validate(1, 120)
  age: number;
}

const user = new User();
user.age = 25; // 正常
user.age = 0;  // 抛出错误

装饰器的局限性

虽然装饰器很强大,但也有一些限制:

  1. 不能装饰函数声明(只能装饰类、方法、访问器、属性或参数)
  2. 装饰器不能修改类的结构(比如不能添加或删除类成员)
  3. 目前还是实验性特性,未来可能会有变化

总结

TypeScript装饰器是一个非常强大的元编程工具,它允许我们以声明式的方式为代码添加功能。通过本文的介绍,你应该已经掌握了:

  • 装饰器的基本概念和使用方法
  • 类装饰器、方法装饰器、属性装饰器的区别
  • 如何创建装饰器工厂
  • 装饰器的执行顺序
  • 装饰器在实际项目中的应用场景

装饰器在Angular、NestJS等框架中都有广泛应用,掌握好这个特性,能让你的代码更加优雅和可维护。