Skip to content

什么是装饰器

装饰器是 ES6 的新特性, 简单来说, 装饰器是一种函数, 它可以扩展被装饰器装饰的对象的功能

配置文件开启支持

如果自己手动配置 webpack 总是失败, 建议直接使用 vite

json
{
  // ...
  "experimentalDecorators": true /* 启用装饰器支持 */,
  "emitDecoratorMetadata": true /* 启用装饰器源数据支持 */
}

类装饰器

typescript
// 定义装饰器
function AnimalActions(target: Function) {
  target.prototype.eat = function () {
    console.log("动物需要吃东西");
  };
  target.prototype.breathe = function () {
    console.log("动物需要呼吸氧气");
  };
  return target;
}

// 使用类装饰器
@AnimalActions
class Cat {}

@AnimalActions
class Dog {}

const c = new Cat();
const d = new Dog();

// 调用扩展的功能
c.eat();
d.eat();
c.breathe();
d.breathe();

// 其实, 装饰器 @AnimalAction 就是个语法糖, 其实他的本质就是
// const Cat = AnimalActions(Cat) || Cat;

装饰器工厂

typescript
// 定义装饰器工厂
function BGMFactory(actor: string) {
  return function (target: Function) {
    switch (actor) {
      case "jay":
        target.prototype.playMusic = () => console.log("稻香");
        break;
      case "vae":
        target.prototype.playMusic = () => console.log("燕归巢");
        break;
      case "ChinaBlue":
        target.prototype.playMusic = () => console.log("泪桥");
        break;
    }
  };
}

// 使用装饰器工厂: 应用到不同类的时候传入不同的值
@BGMFactory("jay")
class Video1 {}
new Video1().playMusic();

@BGMFactory("vae")
class Video2 {}
new Video2().playMusic();

@BGMFactory("ChinaBlue")
class Video3 {}
new Video3().playMusic();

方法装饰器

typescript
// target: { constructor:类, [函数名]:函数引用 }, 如果是装饰静态方法, target 是类而不是保存方法和类的对象
// fnName: 被装饰的函数名
// descriptor: 描述符对象, value 保存的是函数的被装饰的函数 {writable: true, enumerable: false, configurable: true, value: ƒ}
function highlightDecorator(
  target: object,
  key: string,
  descriptor: PropertyDescriptor
) {
  console.info("target:", target);
  console.info("fnName:", key);
  console.info("descriptor:", descriptor);
  const fn = descriptor.value;
  descriptor.value = function () {
    // 重新赋值: 从而改变函数的实现, 如要想要传参(比如颜色), 可以使用装饰器工厂
    return `<span style="color:#f00;">${fn()}</span>`;
  };
}

class UserController {
  @highlightDecorator
  public static isVIP() {
    return "isVIP";
  }

  @highlightDecorator
  public index() {
    return "index";
  }
}

console.log(new UserController().index());

属性装饰器

typescript
// target: 如果是实例属性{constructor: 类}, 如果是静态属性, 保存的是类本身而不是对象
// key: 被装饰的属性的key
function propDecorator(target: object | Function, key: string | symbol) {
  console.info("target:", target);
  console.info("key:", key);
}

class User {
  @propDecorator
  public static staticProp: boolean = false;

  @propDecorator
  public instanceProp: string = "tom@qq.com";
}

new User();

参数装饰器

typescript
// target: { constructor:类 }
// fnName: 方法名
// argIndex: 参数位置
function id(target: object, fnName: string, argIndex: number) {
  console.log("target:", target);
  console.log("fnName:", fnName);
  console.log("argIndex:", argIndex);
}

function isEmail(target: object, fnName: string, argIndex: number) {
  console.log("target:", target);
  console.log("fnName:", fnName);
  console.log("argIndex:", argIndex);
}

class User {
  public getUserByEmail(@id id: number, @isEmail email: string) {}
}

new User().getUserByEmail(11, "tom@qq.com");

装饰器的执行顺序

  1. 参数装饰器
  2. 方法装饰器
  3. 类装饰器
typescript
function ValidateDecorator(
  target: object,
  method: string | symbol,
  descriptor: PropertyDescriptor
) {
  console.info("[ValidateDecorator]target:", target);
  console.info("[ValidateDecorator]method:", method);
  console.info("[ValidateDecorator]descriptor:", descriptor);
}

function RequiredDecorator(
  target: object,
  method: string | symbol,
  index: number
) {
  console.info("[RequiredDecorator]target:", target);
  console.info("[RequiredDecorator]method:", method);
  console.info("[RequiredDecorator]index:", index);
}

function ControllerDescorator(
  target: object,
  key: string | symbol,
  descriptor: PropertyDescriptor
) {
  console.info("[ControllerDescorator]target:", target);
  console.info("[ControllerDescorator]method:", key);
  console.info("[ControllerDescorator]descriptor:", descriptor);
}

@ControllerDescorator
class UserController {
  @ValidateDecorator
  getUser(name: string, @RequiredDecorator id: number, email: string) {}
}

new UserController().getUser("1", 2, "3");

利用 reflect-metadata 实现参数验证

typescript
// 了解: reflect-metadata
import "reflect-metadata";

const obj = {
  id: 1001,
  email: "1001@qq.com",
};

const emailMetadata = {
  company: "tencent",
  createdAt: "2002",
  before: "foxmail",
};

Reflect.defineMetadata("email_infos", emailMetadata, obj, "email");

// 源对象: {id: 1001, email:"1001@qq.com"}
console.info(obj);

// 根据原对象的属性找定义的元数据: {company: 'tencent', createdAt: '2002', before: 'foxmail'}
console.info(Reflect.getMetadata("email_infos", obj, "email"));
typescript
import "reflect-metadata";

function RequiredDecorator(
  target: object,
  method: string | symbol,
  index: number
) {
  const requiredParams: number[] = [];
  requiredParams.push(index);
  Reflect.defineMetadata("required", requiredParams, target, method);
  // 给 target(UserController) 的 method(getUser) 定义元数据: required,
  // 值为 requiredParams, 所有被这个装饰器装饰的参数, 都会将参数在函数形
  // 参列表的 index 放入到 requiredParams 中, 而这个值又会定义到元数据中
}

function ValidateDecorator(
  target: object,
  method: string | symbol,
  descriptor: PropertyDescriptor
) {
  // 获取 UserController 的 getUser 中存的元数据 required
  // 重写 descriptor.value, 让他有验证参数的功能
  // 因为: 装饰器的执行顺序是(先执行参数装饰器, 后执行方法装饰器)
  // 所以: 在这个装饰器执行的时候, 所有被 RequiredDecorator 装饰的参数
  // 的 index 都被定义到了元数据中
  const requiredParams = Reflect.getMetadata("required", target, method);
  const emptyValues = [undefined, null, ""];
  // 可以用装饰器工厂, 将这个emptyValues参数传递进来, 让代码更灵活
  descriptor.value = (...args: any[]) => {
    for (const paramIndex of requiredParams) {
      if (emptyValues.includes(args[paramIndex])) {
        throw new TypeError("[ValidateDecorator]请传入必要的参数");
      }
    }
  };
}

class UserController {
  @ValidateDecorator
  getUser(name: string, @RequiredDecorator id: number, email: string) {}
}

// new UserController().getUser("1", undefined, "3");
new UserController().getUser("1", 2, "3");

Released under the MIT License.