TypeScript 装饰器和依赖注入实现

1,222 阅读4分钟

TypeScript 装饰器和依赖注入实现

装饰器种类

TypeScript 装饰器4种,分别是类装饰器方法装饰器属性装饰器参数装饰器。类型定义如下:

// 内置定于在 lib.es5.d.ts 文件中,可以直接使用。

declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

类中不同声明上的装饰器将按以下规定的顺序应用:

参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个实例成员。 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个静态成员。 参数装饰器应用到构造函数。 类装饰器应用到类。

当存在多个装饰器来装饰同一个声明时,则会有以下的顺序:

  1. 首先,由上至下依次对装饰器表达式求值,得到返回的真实函数(如果有的话)
  2. 而后,求值的结果会由下至上依次调用 (有点类似洋葱模型)
function foo() {
    console.log("foo in"); // 1
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("foo out"); // 4
    }
}

function bar() {
    console.log("bar in"); // 2
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("bar out"); // 3
    }
}

class A {
    @foo()
    @bar()
    method() {}
}

// foo in
// bar in
// bar out
// foo out

Reflect Metadata

使用条件

目前想要使用,我们还需要安装reflect-metadata与在tsconfig.json中启用emitDecoratorMetadata选项。

// 入口文件 index.ts
import 'reflect-metadata';
// tsconfig.json
{
  "compilerOptions": {
    "experimentalDecorators": true, // 开启实验性 TypeScript 装饰器
    "emitDecoratorMetadata": true,  // 在编译阶段为类或类属性添加了元数据
  }
}

emitDecoratorMetadata选项为类或类属性添加了元数据后,构造函数的原型(或是构造函数,根据静态成员还是实例成员决定)会具有[[Metadata]]属性,该属性内部包含一个Map结构,键为属性键,值为元数据键值对。

Reflect.defineMetadata(metadataKey, metadataValue, target[, propertyKey])
// 其数据结构可表示如下:
WeakMap {
  target: Map {
    propertyKey: Map {
      metadataKey: metadataValue
    }
  }
}

内置元数据

在 tsconfig.json 中开启了 emitDecoratorMetadata 选项,此时,TypeScript 在编译时定义一些 元数据设计键(TypeScript 夹带的私货,目前babel并没有此feature),目前可用的有:

  1. 属性类型元数据 design:typeReflect.getMetadata("design:type", target, key)用于获取类属性的类型
  2. 参数类型元数据 design:paramtypesReflect.getMetadata("design:paramtypes", target, key)用于获取方法参数的类型
  3. 返回类型元数据 design:returntypeReflect.getMetadata("design:returntype", target, key)用于获取返回值的类型
const MyClassDecorator: ClassDecorator = (target: any) => {
  const type = Reflect.getMetadata('design:type', target);
  console.log(`类[${target.name}] design:type = ${type && type.name}`);
  const paramTypes: any[] = Reflect.getMetadata('design:paramtypes', target);
  console.log(`类[${target.name}] design:paramtypes =`, paramTypes && paramTypes.map(item => item.name));
  const returnType = Reflect.getMetadata('design:returntype', target)
  console.log(`类[${target.name}] design:returntype = ${returnType && returnType.name}`);
};
// @ts-ignore
const MyPropertyDecorator: PropertyDecorator = (target: any, key: string) => {
  const type = Reflect.getMetadata('design:type', target, key);
  console.log(`属性[${key}] design:type = ${type && type.name}`);
  const paramTypes: any[] = Reflect.getMetadata('design:paramtypes', target, key);
  console.log(`属性[${key}] design:paramtypes =`, paramTypes && paramTypes.map(item => item.name));
  const returnType = Reflect.getMetadata('design:returntype', target, key);
  console.log(`属性[${key}] design:returntype = ${returnType && returnType.name}`);
};
// @ts-ignore
const MyMethodDecorator: MethodDecorator = (target: any, key: string, descriptor: PropertyDescriptor) => {
  const type = Reflect.getMetadata('design:type', target, key);
  console.log(`方法[${key}] design:type = ${type && type.name}`);
  const paramTypes: any[] = Reflect.getMetadata('design:paramtypes', target, key);
  console.log(`方法[${key}] design:paramtypes =`, paramTypes && paramTypes.map(item => item.name));
  const returnType = Reflect.getMetadata('design:returntype', target, key)
  console.log(`方法[${key}] design:returntype = ${returnType && returnType.name}`);
};
// @ts-ignore
const MyParameterDecorator: ParameterDecorator = (target: any, key: string, paramIndex: number) => {
  const type = Reflect.getMetadata('design:type', target, key);
  console.log(`参数[${key} - ${paramIndex}] design:type = ${type && type.name}`);
  const paramTypes : any[] = Reflect.getMetadata('design:paramtypes', target, key);
  console.log(`参数[${key} - ${paramIndex}] design:paramtypes =`, paramTypes && paramTypes.map(item => item.name));
  const returnType = Reflect.getMetadata('design:returntype', target, key)
  console.log(`参数[${key} - ${paramIndex}] design:returntype = ${returnType && returnType.name}`);
};
@MyClassDecorator
class MyClass {
  @MyPropertyDecorator
  myProperty: string;
  constructor (myProperty: string) {
    this.myProperty = myProperty;
  }
  @MyMethodDecorator
  myMethod (@MyParameterDecorator index: number, name: string): string {
    return `${index} - ${name}`;
  }
}

// 结果
// 属性[myProperty] design:type = String
// 属性[myProperty] design:paramtypes = undefined
// 属性[myProperty] design:returntype = undefined
// 参数[myMethod - 0] design:type = Function
// 参数[myMethod - 0] design:paramtypes = [ 'Number', 'String' ]
// 参数[myMethod - 0] design:returntype = String
// 方法[myMethod] design:type = Function
// 方法[myMethod] design:paramtypes = [ 'Number', 'String' ]
// 方法[myMethod] design:returntype = String
// 类[MyClass] design:type = undefined
// 类[MyClass] design:paramtypes = [ 'String' ]
// 类[MyClass] design:returntype = undefined

基本类型序列化

  • number 序列化为 Number
  • string 序列化为 String
  • boolean 序列化为 Boolean
  • any 序列化为 Object
  • void 序列化为 undefined
  • Array 序列化为 Array
  • 如果是Tuple,序列化为Array
  • 如果class将它序列化为类构造函数
  • 如果Enum序列化为Number或者String
  • 如果至少有一个呼叫签名,则序列化为 Function
  • 否则序列化为Object(包括接口)

简单实现依赖注入

import 'reflect-metadata';

type Constructor<T = any> = new (...args: any) => T;
type Decorator = ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator;

type Token = string | symbol | Constructor | Function;
interface IProvider {
  type: 'class' | 'value'
  value: any
  instance?: any
}

const TYPE = 'design:type';
const PARAMTYPES = 'design:paramtypes';
const RETURNTYPE = 'design:returntype';

class Provider implements IProvider {
  constructor(public readonly type: 'class' | 'value', public readonly value: any) {
  }
}

class Container {
  private map = new Map<Token, IProvider>();

  inject(token: Token, provider: IProvider) {
    this.map.set(token, provider);
  }

  get<T>(token: Token): T {
    const { map } = this;
    if (map.has(token)) {
      const provider =  map.get(token)!
      if (provider.type === 'value') {
        return provider.value;
      }
      if (provider.type === 'class') {
        if(provider.instance) return provider.instance;
        const instance = new provider.value();
        provider.instance = instance;
        return instance;
      }
      throw new Error('未知type'+ provider.type);
    } else {
      console.log('================');
      console.log('providers  >>>>>>', map);
      console.log('================');
      throw new Error('找不到' + token.toString());
    }
  }
}

// 初始化容器
const container = new Container();
// 可以为容器预设值
container.inject('val', new Provider('value', 'val'))

function Injectable(token?: Token): ClassDecorator {
  return  (target) =>  {
    console.log('================');
    console.log('target  >>>>>>', target);
    console.log('================');
    // 用token 或者用类作key
    container.inject(token ?? target, new Provider('class', target));
  }
}

function Value(token?: Token): PropertyDecorator {
  return  (target, propertyKey) =>  {
    Object.defineProperty(target, propertyKey, {
      get() {
        return container.get(token ?? propertyKey);
      }
    })
  }
}

function Inject(token?: Token): PropertyDecorator {
  return  (target, propertyKey) =>  {
    const type = Reflect.getMetadata(TYPE, target, propertyKey);
    console.log('================');
    console.log('type  >>>>>>', type);
    console.log('================');
    Object.defineProperty(target, propertyKey, {
      get() {
        return container.get(token ?? type);
      }
    })
  }
}

interface ITest {
  test(): void
}

const ITest = Symbol('ITest');

@Injectable(ITest) // 至少要有一个装饰器
class Test implements ITest{
  @Value('val')
  private readonly valTest!: string;

  test() {
    console.log('================');
    console.log('valTest  is', this.valTest);
    console.log('================');
  }
}

@Injectable()
class TestMachine {
  @Inject(ITest)
  readonly test!: Test;

  run() {
    this.test.test();
  }
}

const testMachine = new TestMachine();
testMachine.run();

// ================
//   target  >>>>>> [Function: Test]
// ================
// ================
//   type  >>>>>> [Function: Test]
// ================
// ================
//   target  >>>>>> [Function: TestMachine]
// ================
// ================
//   valTest  is val
// ================

这里是简单实现没有考虑循环依赖等情况,生产中推荐使用成熟的库,如: inversify