# 装饰器
装饰器的使用需要开启 tsconfig.json 的 experimentalDecorators、emitDecoratorMetadata 属性
# 类的装饰器
装饰器本身是一个函数 类装饰器接受的参数是构造函数 类装饰器用 @ 符号来使用
如下我们给类创建一个简单的装饰器
function testDecorator(flag: boolean) {
constructor.prototype.getName = () => {
console.log('testDecorator 装饰器')
}
}
@testDecorator(true)
class Test {}
const test = new Test();
(test as any).getName()
执行发现 正常打印,现在我们再去添加一个装饰器
function testDecorator1(constructor: any) {
console.log('Decorator1');
}
使用如下:
@testDecorator
@testDecorator1
class Test {}
装饰器从下往上执行,从右往左执行 运行发现 打印了 "Decorator1",我们并没有实例化 Test,可是依然执行了,说明了装饰器是在类定义的时候就执行了。
我们现在有这么一个需求,就是根据特定的条件去执行装饰器,这个时候就要用到 工厂模式来 解决
# 装饰器工厂
可以通过包装一层函数再返回一个函数的形式去实现,如下:
function myDecorator(flag: boolean) {
if (flag) {
return function (constructor: any) {
constructor.prototype.getName = () => {
console.log("我是装饰器工厂创建的");
}
}
} else {
return function (constructor: any) { }
}
}
传入 flag ,执行如下:
@myDecorator(false)
class Test {}
const test = new Test();
(test as any).getName()
可以看到,传入 false 执行会报错,传入 true 会正常执行,不过你也能发现在使用 . 的时候并不会提示 getName 方法,还需要我们 断言一下,下面我们使用更高级的方法解决
function testDecorator() {
return function <T extends new (...args: any[]) => any>(constructor: T) {
return class extends constructor {
name = "aotu";
getName() {
return this.name
}
}
}
}
const Test = testDecorator()(
class {
name: string;
constructor(name: string) {
this.name = name
}
}
)
const test = new Test('xk')
console.log(test.getName());
现在再调用方法,发现有提示了
# 方法装饰器
现在给类的方法添加一个装饰器,如下:
function getNameDecorator(targt: any, key: string, descriptor: PropertyDescriptor) {
console.log(targt, key);
}
class Test {
name: string;
constructor(name: string) {
this.name = name
}
@getNameDecorator
getName() {
return this.name
}
}
装饰器方法会接受三个方法
- targt 普通方法 对应的是类的 prototype 静态方法 对应的是类构造函数
- key 当前的属性名 这里就是 getName
- descriptor 成员的属性描述
如下,通过 descriptor 改写 getName 函数
...
descriptor.value = function () {
return 1
}
const test = new Test('aotu')
console.log(test.getName());
打印发现改变方法已经被改写了
# 访问器装饰器
function setDecorator(targt: any, key: string, descriptor: PropertyDescriptor) {
console.log(descriptor);
descriptor.writable = false
}
class Test {
private _age: number;
constructor(age: number) {
this._age = age
}
@setDecorator
get age() {
return this._age
}
set age(age: number) {
this._age = age
}
}
const test = new Test(20)
test.age = 30
console.log(test.age);
发现报错,我们规定了不能重写,因而不能重写
方法装饰器的 descriptor 有 value 和 witable 属性,但没有 get 和 set 属性;访问器装饰器有 get 和 set 属性,但没有 value 和 witable 属性。
# 属性装饰器
function nameDecorator(target: any, key: string): any {
const descriptor: PropertyDescriptor = {
writable: false
}
return descriptor;
}
class Test1 {
@nameDecorator
name = "aotu";
}
const test1 = new Test1()
属性装饰器 函数只接收两个参数,可以返回 descriptor,对属性加以修饰
# 参数装饰器
function paramDecorator(target: any, method: string, paramIndex: number) {
console.log(target, method, paramIndex);
}
class Test {
getInfo(name: string, @paramDecorator age: number) {
console.log(name, age);
}
}
const test = new Test();
test.getInfo('aotu', 30);
- target 对应的 prototype
- method 方法名
- paramIndex 参数所在的位置
# reflect-metadata
在定义类或者类方法的时候,可以设置一些元数据,我们可以获取 到在类与类方法上添加的元数据,用的方法就是 Reflect Metadata。 元数据指的是描述东西时用的数据。
安装一下 npm 包
npm install reflect-metadata --save
下来我们给一个类添加一个简单的元数据、并去获取它
import 'reflect-metadata'
function Controller(target: any) {
for (const key in target.prototype) {
const handle = target.prototype[key]
const path = Reflect.getMetadata('path', target.prototype, key)//获取存的路径
if (path) {
console.log("获取到了path:", path);
}
handle()
}
}
function get(path: string) {
return function (target: any, propertyKey: string) {
Reflect.defineMetadata('path', path, target, propertyKey)
//'path' 映射的key值
//path 映射key对应的value值
//target 映射的类
//propertyKey 实例上的属性
}
}
@Controller
class Test {
@get('/login')
login() {
console.log('调用login接口');
}
logout(){
console.log('调用logout接口');
}
}
如上我们通过装饰器工厂给方法添加了一个 get 装饰器并传入了路径 path、通过 Reflect 给类添加了一个元属性 path. 启动 文件可以看到有 get 装饰器的 path 在 Controller 会被拦截打印出来. 有了这个特性、我们就能做很多事情了