TypeScript 装饰器和依赖注入实现
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;
类中不同声明上的装饰器将按以下规定的顺序应用:
参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个实例成员。 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个静态成员。 参数装饰器应用到构造函数。 类装饰器应用到类。
当存在多个装饰器来装饰同一个声明时,则会有以下的顺序:
- 首先,由上至下依次对装饰器表达式求值,得到返回的真实函数(如果有的话)
- 而后,求值的结果会由下至上依次调用 (有点类似洋葱模型)
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),目前可用的有:
- 属性类型元数据
design:type
:Reflect.getMetadata("design:type", target, key)
用于获取类属性的类型 - 参数类型元数据
design:paramtypes
:Reflect.getMetadata("design:paramtypes", target, key)
用于获取方法参数的类型 - 返回类型元数据
design:returntype
:Reflect.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
。
转载自:https://juejin.cn/post/7182514433233518629