TypeScript 装饰器

装饰器是一种特殊类型的声明,它可以附加到类声明、方法、属性或参数上,用来修改类的行为。

类装饰器

类装饰器在类声明之前被声明,应用于类构造函数,可以用来观察、修改或替换类定义。类装饰器可以叠加。

类装饰器示例

  1. 定义一个类装饰器 moveDecorator
  2. 装饰器接收目标类构造函数作为参数
  3. 通过修改原型对象为目标类添加新方法 getPosition
// 定义一个类装饰器
const moveDecorator: ClassDecorator = (target: Function) => {
  target.prototype.getPosition = (): { x: number; y: number } => {
    return { x: 100, y: 100 };
  };
};
// 使用装饰器
@moveDecorator
class Tank {
  public getPosition() {}
}
const t = new Tank();
console.log(t.getPosition());

类装饰器工厂

  1. 装饰器工厂是一个返回装饰器函数的函数
  2. 可以根据传入参数返回不同的装饰器实现
  3. 使装饰器更加灵活和可配置
const MusicDecoratorFactory = (type: string): ClassDecorator => {
  switch (type) {
    case 'Tank':
      return (target: Function) => {
        target.prototype.playMusic = () => {
          console.log('坦克音乐');
        };
      };
    case 'player':
      return (target: Function) => {
        target.prototype.playMusic = () => {
          console.log('玩家音乐');
        };
      };
    default:
      return (target: Function) => {
        target.prototype.playMusic = () => {
          console.log('其他音乐');
        };
      };
  }
};

@MusicDecoratorFactory('Tank')
class Tank {}

@MusicDecoratorFactory('player')
class player {}

方法装饰器

方法装饰器声明在一个方法声明之前,应用于方法的属性描述符,可以用来观察、修改或替换方法定义。

方法装饰器示例

  1. 方法装饰器接收三个参数:
    • target: 目标类的原型对象
    • propertyKey: 被装饰的方法名
    • descriptor: 方法的属性描述符
  2. 可以通过修改 descriptor.value 来改变方法行为
const showDecoretor: MethodDecorator = (
  arget: Object, // 目标类的原型对象
  propertyKey: string | symbol, //被装饰的方法名
  descriptor: TypedPropertyDescriptor<any> //被装饰方法的属性描述符
) => {
  descriptor.value = '萧俊介';
};

class User {
  @showDecoretor
  public show() {
    console.log('alrcly');
  }
}

方法装饰器工厂

  1. 返回一个方法装饰器函数
  2. 可以根据传入参数定制装饰器行为
  3. 示例中实现了方法调用的延迟执行
const showDecoratorFactory =
  (times: number): MethodDecorator =>
  (arget: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<any>) => {
    const method = descriptor.value;
    descriptor.value = () => {
      setTimeout(() => {
        method();
      }, times);
    };
  };

属性装饰器 和 参数装饰器

属性装饰器声明在一个属性声明之前,参数装饰器声明在一个参数声明之前。它们可以用来观察、修改或替换属性/参数的定义。

属性装饰器示例

  1. 属性装饰器接收两个参数:
    • target: 目标类的原型对象
    • propertyKey: 被装饰的属性名
  2. 可以用来观察或修改属性定义
// 属性装饰器
const PropDecorator: PropertyDecorator = (
  target: Object, // 目标类的原型对象
  propertyKey: string | symbol // 被装饰的属性的名称
) => {
  console.log(propertyKey);
};
class HD {
  @PropDecorator
  public name: string | undefined;
}

参数装饰器示例

  1. 参数装饰器接收三个参数:
    • target: 目标类的原型对象
    • propertyKey: 被装饰的方法名(构造函数参数时为 undefined)
    • parameterIndex: 参数在参数列表中的索引
  2. 可以用来观察或修改参数定义
const ParamsDecocrator: ParameterDecorator = (
  target: Object, // 目标类的原型对象
  propertyKey: string | symbol | undefined, // 被装饰的方法的名称(字符串或符号),如果装饰的是构造函数参数,则为 undefined
  parameterIndex: number //  被装饰的参数在参数列表中的索引
) => {
  console.log(parameterIndex);
};

class HD {
  public show(id: number, @ParamsDecocrator content: string) {}
}

属性访问器动态转换对象属性

  1. 通过 Object.defineProperty 修改属性访问器
  2. 可以在 getter/setter 中添加自定义逻辑
  3. 示例实现了将属性值自动转换为小写
// 属性装饰器
const LowerDecorator: PropertyDecorator = (target: Object, propertyKey: string | symbol) => {
  let value: string;
  Object.defineProperty(target, propertyKey, {
    get: () => {
      return value.toLowerCase();
    }, 
    set: (v) => {
      value = v;
    },
  });
};
//
class HD {
  @LowerDecorator
  public title: string | undefined;
}

const obj = new HD();
obj.title = 'ALrcLY';
console.log(obj.title);

元数据

reflect-metadata 是一个用于在 TypeScript 中支持反射元数据的库。它允许你在运行时访问和操作类、方法、属性等的元数据。元数据是附加到代码上的额外信息,通常用于描述代码的行为或配置。

TypeScript 中,reflect-metadata 通常与装饰器(Decorators)一起使用,以便在类、方法或属性上添加和读取元数据。这个库是 TypeScript 实现装饰器元数据功能的基础。

使用场景

  • 依赖注入:在框架中自动注入依赖项。
  • 序列化/反序列化:根据元数据自动处理对象的序列化和反序列化。
  • ORM 映射:将类属性映射到数据库字段。

使用示例

import 'reflect-metadata';

class MyClass {
  @Reflect.metadata('key', 'value')
  myMethod() {
    console.log('Hello, World!');
  }
}

const metadataValue = Reflect.getMetadata('key', MyClass.prototype, 'myMethod');
console.log(metadataValue); // 输出:value

在这个例子中,@Reflect.metadata('key', 'value') 装饰器将元数据 'value' 附加到 myMethod 方法上,然后通过 Reflect.getMetadata 在运行时读取这个元数据。

注意事项

  • reflect-metadata 需要在 TypeScript 项目中启用 emitDecoratorMetadata 选项。
  • 这个库主要用于高级场景,普通应用开发中可能不需要直接使用它。

通过 reflect-metadata,你可以更灵活地控制和管理代码的元数据,从而实现更复杂的功能。

Reflect.defineMetadataReflect.getMetadata 是 TypeScript 中的元数据 API,通常用于在类、方法或属性上附加和获取元数据。这些元数据可以用于验证、依赖注入等场景。

使用 Reflect 的 defineMetadata 与 getMetadata 配置验证数据

import 'reflect-metadata';

const RequiredDecorator: ParameterDecorator = (
  target: Object,
  propertyKey: string | symbol | undefined,
  parameterIndex: number
) => {
  if (propertyKey === undefined) {
    throw new Error('propertyKey cannot be undefined');
  }
  let requiredParams: number[] = Reflect.getMetadata('required', target, propertyKey) || [];
  requiredParams.push(parameterIndex);
  Reflect.defineMetadata('required', requiredParams, target, propertyKey);
};

const validateDecorator: MethodDecorator = (
  target: Object,
  propertyKey: string | symbol,
  descriptor: TypedPropertyDescriptor<any>
) => {
  const method = descriptor.value;
  descriptor.value = function () {
    const requiredParams = Reflect.getMetadata('required', target, propertyKey) || [];
    requiredParams.forEache((index: any) => {
      if (index > arguments.length || arguments[index] === undefined) {
        throw new Error('Error');
      }
    });
    return method.apply(this, arguments);
  };
};

class User {
  @validateDecorator
  find(name: string, @RequiredDecorator id: number) {
    console.log(id);
  }
}