类的装饰器
随着TypeScript和ES6里引入了类,在一些场景下我们需要额外的特性来支持标注或修改类及其成员。 装饰器(Decorators)为我们在类的声明及成员上通过元编程语法添加标注提供了一种方式。TypeScript里已做为一项实验性特性予以支持。
注意:装饰器是一项实验性特性,在未来的版本中可能会发生改变。
要启用实验性的装饰器特性,必须在命令行或tsconfig.json
里启用experimentalDecorators
编译器选项:
命令行:
1
| tsc --target ES5 --experimentalDecorators
|
tsconfig.json:
1 2 3 4
| "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true, }
|
装饰器
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression
这种形式,expression
求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
- 类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
- 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
例如,有一个@test
装饰器,可以这样定义test函数:
1 2 3
| function test(constructor: any) { console.log(constructor); }
|
装饰器组合
多个装饰器可以同时应用到一个声明上:
当多个装饰器应用于一个声明上,它们求值方式与复合函数相似。在这个模型下,当复合f和g时,复合的结果(f ∘ g)(x)等同于f(g(x)),也就是按照g(x)>f(x)的顺序执行。
装饰器工厂
如果我们要定制一个修饰器如何应用到一个声明上,我们得写一个装饰器工厂函数。 装饰器工厂就是一个简单的函数,它返回一个表达式,以供装饰器在运行时调用。
可以通过以下方式定义一个装饰器工厂函数:
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
| function test1(show: boolean) { if (show) { return function (target: any) { name = 'change neo'; target.prototype.getName = () => { console.log('getName'); } } } else { return function (constructor: any) { } } }
@test1(true) class UseDecorator { name: string; constructor(name: string) { this.name = name; console.log(this.name); } }
const useDecorator = new UseDecorator('neo') console.log((useDecorator as any).getName())
|
PS:上面的方式定义装饰器后使用装饰器内定义的getName方法,ts会提示找不到,只能通过断言将useDecorator断言为any类型后使用
下面提供一种ts能够识别装饰器中方法的方式:
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
|
function test1() { return function <T extends new (...args: any[]) => any>(target: T) { return class extends target { name = 'neo' getName() { return this.name } } } }
const Test = test1()( class UseDecorator { name: string; constructor(name: string) { this.name = name; console.log(this.name); } } )
console.log(new Test('test').getName());
|
方法装饰器
方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。 方法装饰器不能用在声明文件( .d.ts
),重载或者任何外部上下文类中。
- 第一个参数: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 第二个参数: 是属性的名称
- 第三个参数: 是方法的描述修饰方法
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
|
function getNameDecorator(target: any, key: string, descriptor: PropertyDescriptor) { descriptor.writable = false; descriptor.value = function () { return 'decorator'; } descriptor.enumerable = false; descriptor.configurable = false; }
class UseDecorator { name: string; constructor(name: string) { this.name = name; } @getNameDecorator getName() { return this.name; } }
const useDecorator = new UseDecorator('neo'); console.log(useDecorator.getName());
|
访问器的装饰器
访问器装饰器声明在一个访问器的声明之前(紧靠着访问器声明)。 访问器装饰器应用于访问器的 属性描述符并且可以用来监视,修改或替换一个访问器的定义。 访问器装饰器不能用在声明文件中(.d.ts),或者任何外部上下文类里。
- 第一个参数: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 第二个参数: 是属性的名称
- 第三个参数: 是方法的描述修饰方法
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
|
function getNameDecorator(target: any, key: string, descriptor: PropertyDescriptor) { }
class UseDecorator { private _name: string; constructor(name: string) { this._name = name; } @getNameDecorator get name() { return this._name; } set name(name: string) { this._name = name; } }
const useDecorator = new UseDecorator('neo'); useDecorator.name = 'decorator'; console.log(useDecorator.name);
|
注意TypeScript不允许同时装饰一个成员的get和set访问器。取而代之的是,一个成员的所有装饰的必须应用在文档顺序的第一个访问器上。这是因为,在装饰器应用于一个*属性描述符*时,它联合了get和set访问器,而不是分开声明的。
属性装饰器
属性装饰器声明在一个属性声明之前(紧靠着属性声明)。
- 第一个参数: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 第二个参数: 是属性的名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
function nameDecorator(target: any, key: string): any { console.log(target); target[key] = 'neo1'; }
class UseDecorator { @nameDecorator name = 'neo'; }
const useDecorator = new UseDecorator(); console.log(useDecorator.name); console.log((useDecorator as any).__proto__.name);
|
参数装饰器
参数装饰器声明在一个参数声明之前(紧靠着参数声明)。 参数装饰器应用于类构造函数或方法声明。 参数装饰器不能用在声明文件(.d.ts),重载或其它外部上下文类里。
- 第一个参数: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 第二个参数: 成员的名字
- 第三个参数: 参数在函数参数列表中的索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
function paramDecorator(target: any, method: string, paramIndex: number): any { console.log(target, method, paramIndex); }
class UseDecorator { getName(@paramDecorator name: string, age: number) { console.log(name, age); } }
const useDecorator = new UseDecorator(); useDecorator.getName('neo', 18);
|
注意 参数装饰器只能用来监视一个方法的参数是否被传入。
装饰器的执行顺序
- 属性装饰器先执行,从上往下,谁先写先执行谁
- 有方法装饰器有参数装饰器时,先执行参数装饰器,从方法的最后一个参数开始,逐个向前执行。
- 方法装饰器,从方法的最后一个开始,逐个向前执行
- 类装饰器从上到下执行
下面是个装饰器执行顺序示例:
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 46 47 48 49 50 51 52 53
|
function classDecorator1(target: any) { console.log('类装饰器 1 executed'); }
function classDecorator2(target: any) { console.log('类装饰器 2 executed'); }
function methodDecorator(target: any, key: string, descriptor: PropertyDescriptor) { console.log(`方法装饰器 for ${key} executed`); }
function methodDecorator1(target: any, key: string, descriptor: PropertyDescriptor) { console.log(`方法装饰器1 for ${key} executed`); }
function propertyDecorator(target: any, key: string) { console.log(`属性装饰器 for ${key} executed`); }
function parameterDecorator(target: any, key: string, index: number) { console.log(`参数装饰器 for ${key}, parameter index: ${index} executed`); }
@classDecorator1 @classDecorator2 class ExampleClass { @propertyDecorator property: string; @propertyDecorator property1: string;
constructor() { this.property = '示例属性'; this.property1 = '示例属性1'; }
@methodDecorator @methodDecorator1 exampleMethod(@parameterDecorator parameter: string, @parameterDecorator parameter1: string) { console.log('exampleMethod方法执行'); } }
const exampleInstance = new ExampleClass(); exampleInstance.exampleMethod('参数1', '参数2');
|
输出结果为:
1 2 3 4 5 6 7 8 9
| 属性装饰器 for property executed 属性装饰器 for property1 executed 参数装饰器 for exampleMethod, parameter index: 1 executed 参数装饰器 for exampleMethod, parameter index: 0 executed 方法装饰器1 for exampleMethod executed 方法装饰器 for exampleMethod executed 类装饰器 2 executed 类装饰器 1 executed exampleMethod方法执行 ————类构建完成,执行类中方法
|
装饰器使用场景示例
捕获变量中不包含对应属性时的异常信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const userInfo:any = undefined; class UseDecorator { getName() { try { return useInfo.name; } catch (error) { console.log('useInfo.name undefined'); } } getAge() { try { return useInfo.age; } catch (error) { console.log('useInfo.age undefined'); } } }
const useDecorator = new UseDecorator(); useDecorator.getName(); useDecorator.getName();
|
这样多个方法需要补货异常时,代码会冗余,可以通过方法装饰器统一补货异常
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
| const useInfo: any = undefined;
function catchError(msg: string) { return function (target: any, key: string, descriptor: PropertyDescriptor) { const fn = descriptor.value; descriptor.value = function () { try { fn(); } catch (error) { console.log(msg); } } } }
class UseDecorator { @catchError('useInfo.name 不存在') getName() { return useInfo.name; } @catchError('useInfo.age 不存在') getAge() { return useInfo.age; } }
const useDecorator = new UseDecorator(); useDecorator.getName(); useDecorator.getAge();
|