TypeScript元编程实现对象函数参数类型检查

环境配置

TypeScript配置:

1
2
3
4
5
6
7
8
9
10
// tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
// 支持装饰器语法
"experimentalDecorators": true,
// 支持元数据
"emitDecoratorMetadata": true
}
}

Reflect-metadata 获取元数据

使用装饰器+Reflect-metadata,能获取元数据。这属于元编程。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import "reflect-metadata";

/**
* 3种常见的从装饰器上元信息
*/
export function metadataDecorator(target: any, propertyKey: string) {
console.log(">>>");
console.log(Reflect.getMetadata("design:type", target, propertyKey));
console.log(Reflect.getMetadata("design:paramtypes", target, propertyKey));
console.log(Reflect.getMetadata("design:returntype", target, propertyKey));
}

export class Duty {
private readonly name: string;
private readonly createTime: number;
constructor() {
this.name = "duty info";
this.createTime = Date.now();
}
}

export class Runner {
@metadataDecorator
private readonly version: string;

constructor() {
this.version = "1.0.0";
}

@metadataDecorator
run(duty: Duty): string {
return "";
}
}

上述代码的执行结果是:

1
2
3
4
5
6
7
8
>>>
[Function: String]
undefined
undefined
>>>
[Function: Function]
[ [Function: Duty] ]
[Function: String]

可以看出来:

  • design:type 代表成员类型
  • design:paramtypes 代表函数入参类型
  • design:returntype 代表函数返回类型

实现函数参数类型检查装饰器

实现一个用于检查函数的参数类型的装饰器:@paramTypeDecorator 。被装饰的函数,无须在函数内部显式的检查数据类型。

此装饰器的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const map = new Map();
export function paramTypeDecorator(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const paramTypes = Reflect.getMetadata(
"design:paramtypes",
target,
propertyKey
); // 获得函数参数类型
map.set(propertyKey, paramTypes); // 保存函数的参数类型

const originalFunc = descriptor.value;
// 修改函数的模型行为,在运行前,读取函数的参数类型,并且进行检查
descriptor.value = function (...args: any[]) {
const paramTypes = map.get(propertyKey);
for (let i = 0; i < args.length; ++i) {
if (!(args[i] instanceof paramTypes[i])) {
throw new TypeError(
`Params[${i}] type should be ${paramTypes[i]?.name}`
);
}
}
const result = originalFunc.call(this, ...args);
return result;
};
}

看下在Runner中的使用效果:

1
2
3
4
5
6
7
8
9
10
11
export class Runner {
// ... 其它部分和上面的Runner类似

@paramTypeDecorator
addDuty(duty: Duty) {}
}

const runner = new Runner();
const duty = new Duty();
// 能通过参数类型检查
runner.addDuty(duty);

这段代码会正常运行,类型检查成功。如果最后一句换成:runner.addDuty({} as any) ,那么会报错,输出:TypeError: Params[0] type should be Duty