使用理解
在 JavaScript 中,装饰器的作用可以类比 Java 的注解。但其更多的类似于 AOP 语法糖,因为其本身不需要指定处理器,装饰器在定义时即可对目标进行环绕增强,可以有效地剥离出与业务无关的模板代码,减少冗余。其本质上是一个特定类型的函数,通过 @函数名
的方式使用。
装饰器类型
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
| interface TypedPropertyDescriptor<T> { enumerable?: boolean; configurable?: boolean; writable?: boolean; value?: T; get?: () => T; set?: (value: T) => void; } 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;
|
通过 TypeScript 的声明文件,可以清晰的看到装饰器类型定义,对于不同类型的装饰器,其方法参数总是固定的且通过上下文注入。换言之,装饰器本身是不支持的自定义参数的,为了突破这一限制,通常使用工厂模式,即通过工厂函数接收参数,并返回一个装饰器函数。
装饰器工厂
上文提到,工厂模式并不是装饰器的过度设计,而是对其本身功能的再次加强,是为了提供可自定义入参。下面是一个方法装饰器工厂的一般形式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function methodDecoratorFactory(msg: string) { return funtion(target: Object, propertyKey: string, decorator: TypedPropertyDescriptor<any>) { const fn = decorator.value; decorator.value = function() { const result = fn.call(this, ...arguments) return result; } return decorator; } }
|
实践意义
日志装饰器
记录方法的入参、出参以及执行时间。
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
| export function log(hander?: Function) { const loggerInfo = Object.seal({ method: "", input: "", output: "", custom: "", timeuse: "", }); const timeUse = TimeUse.get(); return function ( target: Object, key: string, descriptor: TypedPropertyDescriptor<any> ): TypedPropertyDescriptor<any> { const oldValue = descriptor.value; descriptor.value = async function () { loggerInfo.method = key; const args: Array<any> = []; for (let index in arguments) { args.push(arguments[index]); } loggerInfo.input = args.join(","); const value = await oldValue.apply(this, arguments); loggerInfo.output = value; hander && (loggerInfo.custom = hander(loggerInfo.input, loggerInfo.output) || "");
loggerInfo.timeuse = `${timeUse.off()}ms`; console.debug(loggerInfo); return value; }; return descriptor; }; }
|
加载装饰器
在异步请求数据时显示加载动画。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| export const loading = function (message: string) { return function ( target: Object, propertyKey: string, decorator: TypedPropertyDescriptor<any> ) { const fn = decorator.value; decorator.value = async function () { try { Loading.show({ message }); const resp = await fn.apply(this, arguments); return resp; } finally { Loading.hide(); } }; return decorator; }; };
|
注意事项
装饰器只能在类及其成员属性上使用,不能作用在静态方法(类方法)上。