参数装饰器作用
可以借助参数装饰器,提前记录参数的某些信息。
实现思路
首先,参数装饰器单独声明是没用的。需要在其定义中,设定某个参数的要求(元信息metadata),例如此参数不能为空、必须为某个类型。
然后借助函数装饰器改写函数运行逻辑,这里可以根据参数装饰器保存的对参数的要求,在函数运行前一个个校验。
总结来说,参数装饰器记录参数元信息,函数装饰器读取元信息并且进行校验。
代码实现
被required
装饰过的函数参数,它们的位置会作为当前方法的metadata保存下来。
被validateEmptyStr
装饰过的函数,它的默认行为会被改写。会在运行前遍历参数,并且检查其是否在当前方法的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 35 36 37 38 39 40 41 42 43 44 45
| const requiredMetadataKey = Symbol.for('router:required')
const required = function (target, propertyKey: string, parameterIndex: number) { const rules = Reflect.getMetadata(requiredMetadataKey, target, propertyKey) || [] rules.push(parameterIndex) Reflect.defineMetadata(requiredMetadataKey, rules, target, propertyKey) }
const validateEmptyStr = function (target, propertyKey: string, descriptor: PropertyDescriptor) { let method = descriptor.value descriptor.value = function () { let args = arguments const rules = Reflect.getMetadata(requiredMetadataKey, target, propertyKey) as Array<number> if (rules && rules.length) { rules.forEach(parameterIndex => { if (!args[parameterIndex]) throw Error(`arguments${parameterIndex} is invalid`) }) } return method.apply(this, arguments) } }
class User { name: string id: number constructor(name:string, id: number) { this.name = name this.id = id }
@validateEmptyStr changeName (@required newName: string) { this.name = newName } }
|
为什么不使用design:paramtypes
元数据来检查函数参数?
在 TypeScript元编程实现对象函数参数类型检查 一文中,实现参数检验的逻辑只使用了方法装饰器,外加reflect-metadata默认支持的design:paramtypes
方法。相较于本文的实现,看起来更简单。
但是,design:paramtypes
无法识别TS语法的可选参数,例如:changeName(newName?: string)
。元数据上只有newName参数的类型是String,但是没有「是否可选」的信息。