# 装饰器

装饰器的使用需要开启 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 有 valuewitable 属性,但没有 getset 属性;访问器装饰器有 getset 属性,但没有 valuewitable 属性。

# 属性装饰器

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 会被拦截打印出来. 有了这个特性、我们就能做很多事情了

更新时间: 11/13/2020, 8:29:46 PM