TypeScript 装饰器

75 阅读4分钟

学习TS,装饰器在框架应用很广泛如:Nest.js,装饰器是必须掌握的内容,否则连入门都入不了。这里介绍TypeScript中装饰器的相关概念,适合初级小伙伴。

顺序为
  1. 属性 装饰器
  2. 参数 装饰器(普通方法)
  3. 方法 装饰器
  4. 构造函数 中的 方法参数 装饰器
  5. 类 装饰器
  • 属性装饰器 是 从上而下;(同一方法内的)参数装饰器、(同一个类的)类装饰器 是 从后往前 执行
  • 一个方法中,先执行参数装饰器(从后开始),再执行方法装饰器

装饰器概念

装饰器使用 @expression 的形式使用,expression 是一个函数,会在运行时被调用,被装饰的数据会作为参数传入到这个函数中。 代码中的 decorator 就是一个装饰器函数,接收一个 target 参数,decorator 装饰器修饰了 Animal 这个类,那么 Animal 类就被作为 target 参数传入到了 decorator 函数中。

function decorator(target: any) {
  target.say = function () {
    console.log("hello!");
  };
}

@decorator
class Animal {
  static say: Function;
  constructor() {}
}
  
Animal.say(); // hello!

装饰器工厂

装饰器工厂通过 @expression(args) 形式使用,装饰器工厂中的 expression 会返回一个装饰器函数,args 是用户想自定义传入的参数。

function decorator(name: string) {
  return function (target: any) {
    target.say = function () {
      console.log("My name is " + name);
    };
  };
}

@decorator("zhangsan")
class People1 {
  static say: Function;
  constructor() {}
}

People1.say(); // My name is zhangsan

@decorator("lisi")
class People2 {
  static say: Function;
  constructor() {}
}

People2.say(); // My name is lisi

多个装饰器的组合

可以对同一目标应用多个装饰器。

function decoratorName(name: string) {
  return function (target: any) {
    target.sayName = function () {
      console.log("My name is " + name);
    };
  };
}

function decoratorAge(age: number) {
  return function (target: any) {
    target.sayAge = function () {
      console.log("My age is " + age);
    };
  };
}

@decoratorName("zhangsan")
@decoratorAge(20)
class People1 {
  static sayName: Function;
  static sayAge: Function;
  constructor() {}
}

People1.sayName(); // My name is zhangsan
People1.sayAge(); // My age is 20

装饰器类型

装饰器共有五种类型,分别为:1. 类装饰器;2.属性装饰器;3.方法装饰器;4.参数装饰器;5.访问器装饰器;

// 类装饰器
@classDecorator
class Bird {
  // 属性装饰器
  @propertyDecorator
  name: string;

  // 方法装饰器
  @methodDecorator
  fly(
    // 参数装饰器
    @parameterDecorator
    meters: number
  ) {}

  // 访问器装饰器
  @accessorDecorator
  get egg() {}
}
类装饰器

类装饰器在类声明之前被声明,用于类、构造函数,可以用来修改或添加类的属性和方法。

function decorator(param?: string): ClassDecorator {
  return (target: any) => {
    target.say = function () {
      console.log("hello!");
    };
    target.run = function () {
      console.log("I am running.");
    };
  };
}

@decorator()
class Animal {
  static say: Function;
  static run: Function;
  constructor() {}
}

Animal.say(); // hello!
Animal.run(); // I am running.
属性装饰器

属性装饰器声明在一个属性声明之前,它允许你修改类的属性或者获取关于类属性的信息。属性装饰器接收两个参数:类的原型(即类本身)和属性键名。

// 定义一个属性装饰器
function log(target: any, key: string) {
    let originalValue: any;

    Object.defineProperty(target, key, {
        get: function() {
            console.log(`Getting ${key}: ${originalValue}`);
            return originalValue;
        },
        set: function(value) {
            console.log(`Setting ${key} to ${value}`);
            originalValue = value;
        },
        enumerable: true,
        configurable: true
    });
}

// 使用属性装饰器
class Person {
    @log
    name: string;

    constructor(name: string) {
        this.name = name; // 这里会触发setter
    }
}

// 创建Person实例
const person = new Person("Alice");

// 访问name属性
console.log(person.name); // 这里会触发getter
person.name = "Bob"; // 这里会再次触发setter
方法装饰器

方法装饰器用于装饰类的方法,它们可以拦截、修改或增强方法的行为。它接收三个参数:类的原型(即类本身)、方法的名称以及方法的描述符。 通过方法装饰器,将方法设置为不能改写。

// descriptor
{
  value: [Function: originMethod],//属性值
  writable: true,//是否可修改
  enumerable: false,//是否可枚举
  configurable: true //是否可删除
}
function decorator(): MethodDecorator {
  return (target, propertyKey, descriptor) => {
    descriptor.writable = false;
    /*
    // 修改函数体
    let origin = descriptor.value;
    descriptor.value = function (...args: any[]) {
      console.log("this:", this); // Person
      // 迭代所有参数
      args = args.map((arg) => arg + "增加相同内容")
      console.log("args", args) // [ 'zh增加相同内容', 'llm增加相同内容' ]

      // 调用以前的函数
      origin.apply(this, args)
    }
    */
  };
}

class A {
  @decorator()
  originMethod() {
    console.log("I'm Original!");
  }
}

const a = new A();

try {
  a.originMethod = () => {
    console.log("I'm Changed!"); //Cannot assign to read only property 'originMethod'
  };
} catch (e) {}

a.originMethod(); // I'm Original! 并没有被修改
参数装饰器

参数装饰器用于装饰类方法的参数。主要用于元数据的注入、参数的验证或者参数的转换。它接收三个参数:类的原型、方法名以及参数在参数列表中的位置。 通常用于收集参数信息,供其他装饰器使用。

function decorator(params?: any): ParameterDecorator {
  return (target, propertyKey, index) => {
    console.log(target); //{}
    console.log(propertyKey); //say
    console.log(index); //1
  };
}

class Animal {
  say(name: string, @decorator() age?: number) {}
}
const requiredMetadataKey = Symbol('required')

function Required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
  const requiredParameters: number[] = Reflect.getMetadata(requiredMetadataKey, target, propertyKey) || []
  requiredParameters.push(parameterIndex)
  Reflect.defineMetadata(requiredMetadataKey, requiredParameters, target, propertyKey)
}

function Validate(target: Object, propertyKey: string | symbol, descriptor: any) {
  const originFn = descriptor.value

  descriptor.value = function (...args: any[]) {
    const requiredParameters: number[] = Reflect.getMetadata(requiredMetadataKey, target, propertyKey)
    if (requiredParameters) {
      for (let parameterIndex of requiredParameters) {
        if ([undefined, null, ''].includes(args[parameterIndex])) {
          throw new Error(`方法 ${String(propertyKey)} 缺少必填参数`)
        }
      }
    }
    return originFn.apply(this, args)
  }
}

class ExampleClass4 {
  @Validate
  greet(@Required name: string) {
    return `Hello, ${name}`
  }
}

const ec = new ExampleClass4()
ec.greet('')
访问器装饰器
  1. 访问器装饰器类似于方法装饰器,唯一的区别在于描述器中有的key不同
  2. 方法装饰器的描述器的key为:value,writable,enumerable,configurable
  3. 访问器装饰器的描述器的key为:get,set,enumerable,configurable
function configurable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.configurable = value;
  };
}

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

什么时候使用装饰器

  • 通用 Before/After 钩子
  • 监听属性变更或方法调用
  • 转换方法参数
  • 给类添加额外的方法或属性
  • 运行时类型检查
  • 自动编码/解码
  • 依赖注入