目录
TS装饰器与反射详解
- 视频地址: https://www.aliyundrive.com/s/sZLno66XP2S
- 源码地址: https://git.3rcd.com/class/ts-decorator (3R成员登录后获取,否则404)
装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、 访问符、属性或参数上。 装饰器使用 @expression
这种形式,expression
求值后必须为一个函数。它会在运行时被调用,被装饰的声明信息做为参数传入。
装饰器一般用于处理一些与类以及类属性本身无关的逻辑。例如: 一个类方法的执行耗时统计或者记录日志,可以单独拿出来写成装饰器。
如果有使用过 Spring Boot 或者 PHP 的 Symfony 框架的话,就基本知道装饰器的作用分别类似于以上两者的注解和 annotation,而 Node 中装饰器用的比较好的框架是 NestJS。不过不了解也没关系,接下来我就按我的理解讲解一下装饰器的使用。
需要开启装饰器特性请在 tsconfig.json
中开启 experimentalDecorators
编译器选项
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
基本概念
可能有些时候,我们会对传入参数的类型判断、对返回值的排序和过滤、对函数添加节流和防抖或其他的功能性代码,基于多个类的继承,各种各样的与函数逻辑本身无关的、重复性的代码。 比如,我们要在用户登录的时候记录一下登录时间
const logger = (now: number) => console.log(`lasted logged in ${now}`);
class User {
async login() {
await setTimeout(() => console.log('login success'), 100);
logger(new Date().valueOf());
}
}
以上代码把记录日志的代码强行写入登录的逻辑处理,这样代码量越高则代码越冗余。我们需要把日志逻辑单独拿出来,使 login 方法更专注于处理登录的逻辑。接下去我们用高阶函数模拟一下装饰器的原理,以便于后面更好的理解装饰器。
/**
* 使用高阶函数
* 柯里化解构登录与日志记录
*/
type DecoratorFunc = (
target: any,
key: string,
descriptor: PropertyDescriptor,
) => void;
// 模拟的装饰器工厂函数
const createDecorator =
(decorator: DecoratorFunc) => (Model: any, key: string) => {
// 获取即将使用装饰器的类原型
const target = Model.prototype;
// 获取这个原型上某个方法的描述
const descriptor = Object.getOwnPropertyDescriptor(target, key);
// 更改描述,生成新的方法
decorator(target, key, descriptor);
};
const logger: DecoratorFunc = (target, key, descriptor) =>
// 将修改后的函数重新定义到原型链上
Object.defineProperty(target, key, {
...descriptor,
value: async (...args: any[]) => {
try {
return descriptor.value.apply(this, args); // 调用之前的函数
} finally {
const now = new Date().valueOf();
console.log(`lasted logged in ${now}`);
}
},
});
class User {
async login() {
console.log('login success');
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
}
}
export const exp1 = () => {
console.log();
console.log(
'-----------------------示例1:高阶函数柯里化(装饰器内部原理)-----------------------',
);
console.log(
'-----------------------实现登录和日志记录解耦-----------------------',
);
console.log();
const loggerDecorator = createDecorator(logger);
loggerDecorator(User, 'login');
const user = new User();
user.login();
console.log();
console.log('-----------------------示例1:执行完毕-----------------------');
};
// 控制台输出
// login success
// 停顿100ms
// lasted logged in 1571771681793
了解了以上概念,接下去让我们学习真正的装饰器。
装饰器类型
TS中的装饰器有几种类型,如下:
- 参数装饰器
- 方法装饰器
- 访问符装饰器
- 属性装饰器
- 类装饰器
以上每种装饰器分别可以作用于类原型(_prototype_属性)和类本身
类装饰器
TS官方文档 中举了一个类装饰器的例子,也可以看一下。类装饰器其实就是把我们本身的类传入装饰器注解中,并对这个类或类的原型进行一些处理,仅此而已。 例如:
const HelloDerorator = <T extends new (...args: any[]) => any>(
constructor: T,
) => {
return class extends constructor {
newProperty = 'new property';
hello = 'override';
sayHello() {
return this.hello;
}
};
};
@HelloDerorator
export class Hello {
[key: string]: any; // 此处用于防止eslint提示sayHello方法不存在
hello: string;
constructor() {
this.hello = 'test';
}
}
const exp2 = () => {
console.log(
'-----------------------示例2:简单的类装饰器-----------------------',
);
console.log(
'-----------------------动态添加一个sayHello方法以及覆盖hello的值-----------------------',
);
console.log();
const hello = new Hello();
console.log(hello.sayHello());
console.log();
console.log('-----------------------示例2:执行完毕-----------------------');
};
// 控制台打印 override