装饰器是一种特殊类型的声明,它能够附加到类、类的函数、类属性、类函数的参数上,以达到修改类的行为,主要是面向切片编程思维
一、装饰器的分类
-
1、根据装饰器的位置
- 类装饰器
- 类函数装饰器
- 类属性装饰器
- 类函数参数装饰器
-
2、根据装饰器是否有参数
- 无参装饰器(一般装饰器)
- 有参装饰器(装饰器工厂)
二、基础环境的搭建
-
1、创建一个文件夹并且初始化
tsc --init
-
2、配置
tsconfig.json
文件{ "compilerOptions": { // 打开装饰器 "experimentalDecorators": true } }
三、关于几种装饰器的参数
-
1、类装饰器
- 参数就是类本身
function ClsDes(args: string): ClassDecorator { return (target: object) => { console.log(args, target); } }
-
2、类方法装饰器
- 第一个参数是类本身
- 第二个参数是被装饰的方法名
- 第三个参数是属性描述符
function funDesc(args: string): MethodDecorator { return (target: object, methodName: string | symbol, desc: TypedPropertyDescriptor<any> ) => { console.log(target, methodName, desc); } }
-
3、类方法参数装饰器
- 第一个参数是类本身
- 第二个参数是被装饰的函数名
- 第三个参数是参数在方法中的位置
function test1(args: string): ParameterDecorator { return (target: object, methodName: string | symbol, index: number) => { console.log(target, methodName, index, args); } }
-
4、属性装饰器
- 第一个参数是类本身
- 第二个参数是被装饰的属性名
function argDec(args: string):PropertyDecorator { return (target: object, name: string | symbol) => { console.log(args, target, name); } } class Person1 { @argDec('hello') name: string constructor (name: string) { this.name = name; } }
四、类的装饰器
-
1、类装饰器的写法
function desc(target) { console.log('---------------类的装饰器参数 start------------------'); console.log(target); // 输出 [Function: Person]表示当前装饰的类 console.log('---------------类的装饰器参数 end------------------'); } @desc // 使用装饰器 class Person { public name: string | undefined; public age: number | 0; constructor(name, age) { this.name = name; this.age = age; } } let p = new Person('哈哈', 20);
-
2、使用类的装饰器扩展类的属性和方法
function desc(target) { console.log('---------------类的装饰器参数 start------------------'); console.log(target); console.log('---------------类的装饰器参数 end------------------'); return class extends target{ // 在react高阶组件中经常看到这种写法 gender = '男'; say() { console.log(this.name, this.age, this.gender); } } } @desc class Person { public name: string | undefined; public age: number | 0; constructor(name, age) { this.name = name; this.age = age; } } let p = new Person('哈哈', 20); console.log(p); p.say(); /* ---------------类的装饰器参数 start------------------ [Function: Person] ---------------类的装饰器参数 end------------------ class_1 { name: '哈哈', age: 20, gender: '男' } 哈哈 20 男 */
-
3、使用装饰器修改类的构造函数(构造函数的重载、方法重载)
function desc(target) { return class extends target{ name = '我是重载后的'; sayHell() { console.log('我是重载后的', this.name); } } } @desc class Person { public name: string | undefined; public age: number | 0; constructor() { this.name = '哈哈'; this.age = 20; } sayHell() { console.log('hello word', this.name); } } let p = new Person(); console.log(p); p.sayHell();
-
4、装饰器工厂的写法(外部传递参数)
function desc(params: string) { return function (targe: any) { console.log('---------------参数说明 start------------------'); console.log('params', params); console.log('target', targe); console.log('---------------参数说明 end------------------'); // 直接在原型上扩展一个属性 targe.prototype.apiUrl = params; } } @desc('http://www.baidu.com') class P { say() { console.log('说话') } } let p:any = new P(); console.log(p.apiUrl);
五、类函数装饰器
它应用到方法上,可以用来监视、修改、替换该方法
-
1、定义方式
function desc(target, key, descriptor) { console.log('---------------类的装饰器参数 start------------------'); console.log('target', target); // Person { say: [Function] } 表示类的原型 console.log('key', key); // 被装饰的函数名 console.log('descriptor', descriptor); // 被装饰的函数的对象属性 console.log('---------------类的装饰器参数 end------------------'); }
-
2、使用
class Person { public name: string | undefined; public age: number | 0; constructor(name, age) { this.name = name; this.age = age; } @desc say() { console.log('说的方法') } }
-
3、在装饰器中添加类的原型属性和原型方法
function desc(target, key, descriptor) { target.gender = '男'; target.foo = function () { console.log('我是原型上的方法') } } // 测试代码 let p = new Person('哈哈', 20); console.log(p); console.log(Person.prototype); p.say(); console.log(p.gender); // 使用p原型链上的属性 p.foo() // 调用了p原型链上的方法
-
4、使用装饰器拦截函数的调用(替换)
function desc(params: string) { return function (target: any, key: string, descriptor: {[propsName: string]: any}) { // 修改被装饰的函数的 let method = descriptor.value; descriptor.value = function (...args: Array<any>) { args = args.map(it => String(it)); console.log(args); // method.apply(this, args); } } } class Person { public name: string | undefined; public age: number | 0; constructor(name, age) { this.name = name; this.age = age; } @desc('装饰器上的参数') say() { console.log('说的方法') } } let p = new Person('哈哈', 20); console.log(p); p.say(123, 23, '你好');
-
5、使用装饰器拦截函数的调用(附加新的功能)
function desc(params: string) { return function (target: any, key: string, descriptor: {[propsName: string]: any}) { // 修改被装饰的函数的 let method = descriptor.value; descriptor.value = function (...args: Array<any>) { args = args.map(it => String(it)); console.log(args); method.apply(this, args); } } } class Person { public name: string | undefined; public age: number | 0; constructor(name, age) { this.name = name; this.age = age; } @desc('装饰器上的参数') say(...args) { console.log('说的方法', args) } } let p = new Person('哈哈', 20); console.log(p); p.say(123, 23, '你好');
六、类属性装饰器
-
1、定义方式
function desc(target, name) { console.log('---------------类属性装饰器的参数 start------------------'); console.log('target', target, target.constructor); // 表示类的原型 console.log('name', name); // 表示被装饰属性名 console.log('---------------类属性装饰器的参数 end------------------'); } class Person { public name: string | undefined; public age: number | 0; @desc private gender: string | undefined; constructor(name, age) { this.name = name; this.age = age; } } let p = new Person('哈哈', 20); console.log(p);
-
2、在装饰器中修改属性值
function desc(target, name) { target[name] = '女'; } class Person { public name: string | undefined; public age: number | 0; @desc public gender: string | undefined; constructor(name, age) { this.name = name; this.age = age; } say() { console.log(this.name, this.age, this.gender); } } let p = new Person('哈哈', 20); console.log(p); p.say();
七、类函数参数的装饰器
参数装饰器表达式会在运行时候当做函数被调用,以使用参数装饰器为类的原型上附加一些元数据
-
1、使用方式
function desc(params: string) { return function (target: any, key, index) { console.log('---------------参数装饰器 start------------------'); console.log(target); // 类的原型 console.log(key); // 被装饰的名字 console.log(index); // 序列化 console.log('---------------参数装饰器 end------------------'); } } class Person { public name: string | undefined; public age: number | 0; constructor(name, age) { this.name = name; this.age = age; } say(@desc('参数装饰器') age: number) { console.log('说的方法') } } let p = new Person('哈哈', 20); console.log(p); p.say(20);
-
2、为类的原型上添加一些东西
function desc(params: string) { return function (target: any, key, index) { console.log('---------------参数装饰器 start------------------'); console.log(target); // 类的原型 console.log(key); // 被装饰的名字 console.log(index); // 序列化 target.message = params; console.log('---------------参数装饰器 end------------------'); } } class Person { public name: string | undefined; public age: number | 0; constructor(name, age) { this.name = name; this.age = age; } say(@desc('参数装饰器') age: number) { console.log('说的方法') } } let p: any = new Person('哈哈', 20); console.log(p); p.say(20); console.log(p.message)
八、几种装饰器的执行顺序
-
1、测试代码
function logCls(params: string) { return function (target: any) { console.log('4.类的装饰器'); } } function logMehod(params: string) { return function (target: any, key: string, descriptor: {[propsName: string]: any}) { console.log('3.类的函数装饰器'); } } function logParams(params: string) { return function (target: any, name: string) { console.log('1.类属性装饰器'); } } function logQuery(params: string) { return function (target: any, key: string, index: number) { console.log('2.函数参数装饰器'); } } @logCls('类的装饰器') class Person{ @logParams('属性装饰器') public name: string | undefined; @logMehod('函数装饰器') getData(@logQuery('函数参数装饰器') age: number, @logQuery('函数参数装饰器') gender: string) { console.log('----'); } }
-
2、运行结果
1.类属性装饰器 2.函数参数装饰器 3.类的函数装饰器 4.类的装饰器
九、结合Reflect Metadata
在项目中开发
Reflect Metadata
是 ES7
的一个提案,它主要用来在声明的时候添加和读取元数据。TypeScript
在 1.5+ 的版本已经支持它,你只需要安装依赖包,并且在tsconfig.json
中开启配置
安装依赖包
npm i reflect-metadata --save
在tsconfig.json
中开启配置
{
"compilerOptions": {
"emitDecoratorMetadata": true,
}
}
-
1、基本使用
import 'reflect-metadata'; const IN_CLASS = 'inClass'; const in_METHOD = 'inMethod'; @Reflect.metadata(IN_CLASS, 'A') class Test { @Reflect.metadata(in_METHOD, 'B') public hello(): string { return 'hello world'; } } console.log(Reflect.getMetadata(IN_CLASS, Test)); // 'A' console.log(Reflect.getMetadata(in_METHOD, new Test(), 'hello')); // 'B'
-
2、自定义
metadataKey
function classDecorator(): ClassDecorator { return target => { // 在类上定义元数据,key 为 `classMetaData`,value 为 `a` Reflect.defineMetadata('classMetaData', 'a', target); }; } function methodDecorator(): MethodDecorator { return (target, key, descriptor) => { // 在类的原型属性 'someMethod' 上定义元数据,key 为 `methodMetaData`,value 为 `b` Reflect.defineMetadata('methodMetaData', 'b', target, key); }; } @classDecorator() class SomeClass { @methodDecorator() someMethod() {} } Reflect.getMetadata('classMetaData', SomeClass); // 'a' Reflect.getMetadata('methodMetaData', new SomeClass(), 'someMethod'); // 'b'