【ts】装饰器 & 元数据

150 阅读5分钟

定义

  • 装饰器本质是一个函数
  • 通过 @ 语法将函数应用到类、类的属性、方法、访问器或参数上
  • 在代码文件运行时被调用,对被装饰的目标进行扩展、修改或注入额外逻辑

装饰器分类

  • 类装饰器(Class Decorator)
  • 属性装饰器(Property Decorator)
  • 方法装饰器(Method Decorator)
  • 参数装饰器(Parameter Decorator)
  • 访问器装饰器(Accessor Decorator)
// 类装饰器
function classDecorator1(cls) {}
function classDecorator2(cls) {}

// 属性装饰器
function propertyDecorator1(clsPrototype, attrName) {}
function propertyDecorator2(clsPrototype, attrName) {}

// 访问器装饰器
function accessorDecorator1(clsPrototype, accessorName, descriptor) {}
function accessorDecorator2(clsPrototype, accessorName, descriptor) {}

// 方法参数装饰器
function paramDecorator1(clsPrototype, methodName, index) {}
function paramDecorator2(clsPrototype, methodName, index) {}

// 方法装饰器
function methodDecorator1(clsPrototype, methodName, descriptor) {}
function methodDecorator2(clsPrototype, methodName, descriptor) {}

// 装饰器的应用
@classDecorator1
@classDecorator2
class Example {
  @propertyDecorator1
  @propertyDecorator2
  property: string = '';

  @accessorDecorator1
  @accessorDecorator2
  get value() { return this.property; }

  @methodDecorator1
  @methodDecorator2
  method(
    @paramDecorator1 param1: string,
    @paramDecorator2 param2: number
  ) {}
}

装饰器工厂

用于生产装饰器函数的工厂,通过闭包的方式返回装饰器函数,使装饰器函数运行时可以拿到外部传入的数据,以下以类装饰工厂举例,其余装饰器函数工厂也是同理

function classDecoratorFactory(params) {
  return function (cls) {}
}

@classDecoratorFactory('age')
class Example {}

装饰器原理

思路

ts 是 js 的超集,本质上还是通过 js 实现的,装饰器是 ts 独有的语法,可通过将 ts 编译为 js,查看装饰器的实现原理

实现
  1. 修改 ts 配置文件,使编译结果符合预期
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES5", // 编译目标为 es5
    "experimentalDecorators": true // 告诉编译器使用了实验性的装饰器特性,需对装饰器特性进行编译
  }
}
  1. 命令行中执行编译命令
tsc
  1. 得到编译后的 js 代码
"use strict";
/**
 * 装饰逻辑
 */
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  // 参数个数
  // c < 3 ? 类装饰器: (desc === null ? 其他装饰器 : 属性装饰器)
  var c = arguments.length;
  // 装饰器间传递的对象
  var r = c < 3 ?
    target : // 类构造函数
      desc === null ?
        desc = Object.getOwnPropertyDescriptor(target, key) : // 属性描述符对象
        desc; // void 0
  // 当前执行的装饰器函数
  var d;

  // 核心逻辑
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
    r = Reflect.decorate(decorators, target, key, desc); // 引入 reflect-metadata 包后才有该方法
  } else { // 回退逻辑
    for (var i = decorators.length - 1; i >= 0; i--) {
      if (d = decorators[i]) {
        // 装饰器函数要将 r 返回,供给下一个装饰器使用
        // 否则后面的装饰器还是基于原先的 r 进行处理
        r = (c < 3
              ? d(r) 
              : c > 3 
                ? d(target, key, r)
                : d(target, key))
            || r;
      }
    }
  }
  
  // 若为函数/函数参数/访问器装饰器,需更新属性描述符对象
  return c > 3 && r && Object.defineProperty(target, key, r), r;
};

/**
 * 参数装饰器的包装函数:通过闭包的形式传递参数索引
 */
var __param = (this && this.__param) || function (paramIndex, decorator) {
  return function (target, key) {
    decorator(target, key, paramIndex);
  }
};

function classDecorator1(cls) { }
function classDecorator2(cls) { }
function propertyDecorator1(clsPrototype, attrName) { }
function propertyDecorator2(clsPrototype, attrName) { }
function accessorDecorator1(clsPrototype, accessorName, descriptor) { }
function accessorDecorator2(clsPrototype, accessorName, descriptor) { }
function paramDecorator1(clsPrototype, methodName, index) { }
function paramDecorator2(clsPrototype, methodName, index) { }
function methodDecorator1(clsPrototype, methodName, descriptor) { }
function methodDecorator2(clsPrototype, methodName, descriptor) { }

var Example = /** @class */ (function () {
    function Example() {
        this.property = '';
    }
    Object.defineProperty(Example.prototype, "value", {
        get: function () { return this.property; },
        enumerable: false,
        configurable: true
    });
    Example.prototype.method = function (param1, param2) { };

    __decorate([
        propertyDecorator1,
        propertyDecorator2
    ], Example.prototype, "property", void 0);

    __decorate([
        accessorDecorator1,
        accessorDecorator2
    ], Example.prototype, "value", null);

    __decorate([
        methodDecorator1,
        methodDecorator2,
        __param(0, paramDecorator1),
        __param(1, paramDecorator2)
    ], Example.prototype, "method", null);

    Example = __decorate([
        classDecorator1,
        classDecorator2
    ], Example);
    return Example;
}());

装饰器执行顺序

  • 成员装饰器优先于类装饰器,成员装饰器根据声明位置执行
  • 相同装饰目标的多个装饰器:从下到上执行(最靠近目标的先执行)
  • 同一函数多个参数的装饰器:按声明顺序从右到左执行
// property 的装饰器(从下到上执行)
@propertyDecorator2  // 第1个执行
@propertyDecorator1  // 第2个执行

// method() 的参数装饰器
@paramDecorator2  // 第3个执行
@paramDecorator1  // 第4个执行

// method() 的装饰器(从下到上执行)
@methodDecorator2  // 第5个执行
@methodDecorator1  // 第6个执行

// method2() 的参数装饰器  
@paramDecorator4  // 第7个执行
@paramDecorator3  // 第8个执行

// method2() 的装饰器(从下到上执行)
@methodDecorator4  // 第9个执行  
@methodDecorator3  // 第10个执行

// get value() 的装饰器(从下到上执行)
@accessorDecorator2  // 第11个执行
@accessorDecorator1  // 第12个执行

// 类装饰器(从下到上执行)
@classDecorator2  // 第13个执行
@classDecorator1  // 第14个执行

元数据

元数据(Metadata)是指描述数据的数据

元数据 API

ts 本身不直接提供元数据 API,需要依赖 reflect-metadata 库

pnpm add reflect-metadata
import 'reflect-metadata';

// 给数据定义元数据
Reflect.defineMetadata(metadataKey, metadataValue, target)
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey)

// 获取数据的元数据
let result = Reflect.getMetadata(metadataKey, target)
let result = Reflect.getMetadata(metadataKey, target, propertyKey)

// 数据是否存在指定的元数据
let result = Reflect.hasMetadata(metadataKey, target)
let result = Reflect.hasMetadata(metadataKey, target, propertyKey)

// 获取数据上所有元数据的建
let result = Reflect.getMetadataKeys(target)
let result = Reflect.getMetadataKeys(target, propertyKey)

// 删除数据上的元数据
let result = Reflect.deleteMetadata(metadataKey, target)
let result = Reflect.deleteMetadata(metadataKey, target, propertyKey)

装饰器工厂:返回定义元数据的装饰器函数

Reflect.metadata(metadataKey, metadataValue)

// => 简化实现

function metadata(metadataKey: any, metadataValue: any) {
  function decorator(target: Function | Object, propertyKey?: string | symbol) {
    if (typeof propertyKey !== "undefined") {
      // 应用于属性或方法
      Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);
    } else {
      // 应用于类
      Reflect.defineMetadata(metadataKey, metadataValue, target);
    }
  }
  return decorator;
}

// 挂载到 Reflect 上
Reflect.metadata = metadata;
内置元数据

在 tsconfig.json 里配置 emitDecoratorMetadata 选项,会给所有装饰器修饰的内容自动添加内置元数据

  • design:type
  • design:paramtypes
  • design:returntype

上述元数据在不同装饰器中有不同的含义

var __metadata = (this && this.__metadata) || function (k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};

__decorate([
    propertyDecorator1,
    propertyDecorator2,
    __metadata("design:type", String) // 属性类型
], Example.prototype, "property", void 0);
__decorate([
    accessorDecorator1,
    accessorDecorator2,
    __metadata("design:type", Object), // 编译器在编译时无法完全确定返回类型,所以默认使用 Object 作为类型
    __metadata("design:paramtypes", []) // 因为 getter 访问器不接受任何参数,所以参数类型数组为空
], Example.prototype, "value", null);
__decorate([
    methodDecorator1,
    methodDecorator2,
    __param(0, paramDecorator1),
    __param(1, paramDecorator2),
    __metadata("design:type", Function), // 固定为函数类型
    __metadata("design:paramtypes", [String, Number]), // 参数类型
    __metadata("design:returntype", void 0) // 返回类型
], Example.prototype, "method", null);
Example = __decorate([
    classDecorator1,
    classDecorator2
], Example);