TypeScript08:装饰器、元数据

什么是装饰器

decorators在TS中是一种可以不修改类代码的基础上通过添加标注的方式来对类型进行扩展的一种方式

  • 减少代码量
  • 提高代码扩展性、可读性和可维护性
  • 只能在类中使用

语法

装饰器的使用极其的简单

  • 装饰器本质就是一个函数
  • 通过特定语法在特定的位置调用装饰器函数即可对数据(类、方法、甚至参数等)进行扩展

启用装饰器特性

  • experimentalDecorators: true
// 装饰器函数
function log(target: Function, type: string, descriptor: PropertyDescriptor) {
    let value = descriptor.value;

    descriptor.value = function(a: number, b: number) {
        let result = value(a, b);
        console.log('日志:', {
            type,
            a,
            b,
            result
        })
        return result;
    }
}

// 原始类
class M {
    @log
    static add(a: number, b: number) {
        return a + b;
    }
    @log
    static sub(a: number, b: number) {
        return a - b;
    }
}

let v1 = M.add(1, 2);
console.log(v1);
let v2 = M.sub(1, 2);
console.log(v2);

装饰器种类

装饰器 是一个函数,它可以通过 @装饰器函数 这种特殊的语法附加在 方法访问符属性参数 上,对它们进行包装,然后返回一个包装后的目标对象(方法访问符属性参数

装饰器工作在类的构建阶段,而不是使用阶段

类装饰器

目标

  • 应用于类的构造函数

参数

  • 第一个参数(也只有一个参数)target
    类的构造函数作为其唯一的参数

属性装饰器

访问器装饰器

目标

  • 应用于类的访问器(getter、setter)上

参数

  • 第一个参数
    • 静态方法:类的构造函数
    • 实例方法:类的原型对象
  • 第二个参数
    • 属性名称
  • 第三个参数
    • 方法描述符对象

方法装饰器

目标

  • 应用于类的方法上

参数

  • 第一个参数target
    • 静态方法static:类的构造函数
    • 实例方法:类的原型对象
  • 第二个参数name
    • 被装饰的方法名称
  • 第三个参数descriptor:PropertyDescriptor
    • 方法描述符对象

参数装饰器

目标

  • 应用在参数上

参数

  • 第一个参数
    • 静态方法:类的构造函数
    • 实例方法:类的原型对象
  • 第二个参数
    • 方法名称
  • 第三个参数
    • 参数在函数参数列表中的索引

实例

function d1(target: Function) {
    console.log("---------------d1类装饰器---------------------")
    console.log(target)
    console.log(typeof target)
}

function d2(target: any, name: string) {
    console.log("---------------d2属性装饰器---------------------")
    console.log(typeof target, name)
}

function d3(target: any, name: string, descriptor: PropertyDescriptor) {
    console.log("---------------d3访问器装饰器---------------------")
    console.log(typeof target, name)
    console.log(descriptor)
}
function d4(target: any, name: string, descriptor: PropertyDescriptor) {
    console.log("---------------d4方法装饰器---------------------")
    console.log(typeof target, name)
    console.log(descriptor)
}
function d5(target: any, name: string, index: number) {
    // name 是当前参数所在的方法
    console.log("---------------d5参数装饰器---------------------")
    console.log(typeof target, name)
    console.log(index)
}

@d1
class MyClass {

    @d2
    static property1: number;

    @d2
    a: number

    @d3
    get b() {
        return 1;
    }
    @d3
    static get c() {
        return 2;
    }

    @d4
    public method1(@d5 x: number, @d5 y: number) { };
    @d4
    public static method2() { };
}

执行顺序

---------------d2属性装饰器---------------------
object a
---------------d3访问器装饰器---------------------
object b
{
  get: [Function: get],
  set: undefined,
  enumerable: false,
  configurable: true
}
---------------d5参数装饰器---------------------
object method1
1
---------------d5参数装饰器---------------------
object method1
0
---------------d4方法装饰器---------------------
object method1
{
  value: [Function],
  writable: true,
  enumerable: true,
  configurable: true
}
---------------d2属性装饰器---------------------
function property1
---------------d3访问器装饰器---------------------
function c
{
  get: [Function: get],
  set: undefined,
  enumerable: false,
  configurable: true
}
---------------d4方法装饰器---------------------
function method2
{
  value: [Function],
  writable: true,
  enumerable: true,
  configurable: true
}
---------------d1类装饰器---------------------
[Function: MyClass] { method2: [Function] }
function

装饰器执行顺序分析

  1. 实例装饰器
    属性装饰器 => 访问器装饰器 => 参数装饰器 => 方法装饰器
  2. 静态装饰器
    属性 => 访问器 => 参数 => 方法
  3. 类装饰器

复合装饰器

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

装饰器工厂

如果我们需要给装饰器执行过程中传入一些参数的时候,就可以使用装饰器工厂来实现

  • 通过闭包传参
  • 闭包里返回装饰器函数
function log(type: string) {
    return function log(target: Function, name: string, descriptor: PropertyDescriptor) {
        let value = descriptor.value;
        descriptor.value = function (x: number, y: number) {
            let result = value(x, y);
            console.log({
                type,
                name,
                x,
                y,
                result
            });
            return result;
        }
    }
}

class M {
    @log("log")
    static add(x: number, y: number) {
        return x + y
    }
    @log("storage")
    static sub(x: number, y: number) {
        return x - y
    }
}

let v1 = M.add(1, 2);
console.log(v1);
let v2 = M.sub(1, 2);
console.log(v2);

log

{ type: 'log', name: 'add', x: 1, y: 2, result: 3 }
3
{ type: 'storage', name: 'sub', x: 1, y: 2, result: -1 }
-1

元数据

装饰器 函数中 ,我们可以拿到 方法访问符属性参数 的基本信息,如它们的名称,描述符 等

但是我们想获取更多信息就需要通过另外的方式来进行:元数据

function L(type: string) {
    return function (target: Function) {
        target.prototype.type = type;
    }
}

function log(type?: string) {
    return function log(target: any, name: string, descriptor: PropertyDescriptor) {
        // log 方法装饰器比L类装饰器先执行,想访问L添加到原型链中的内容需要在

        let value = descriptor.value;
        descriptor.value = function (x: number, y: number) {
            let result = value(x, y);
            let _type = type;
            if (!_type) {
                _type = typeof target === "function" ? target.prototype.type : target.type;
            }
            console.log({
                type: _type,
                name,
                x,
                y,
                result
            });
            return result;
        }
    }
}

@L("storage")
class M {
    @log()  // 没传参就用类装饰器的参数
    static add(x: number, y: number) {
        return x + y
    }
    @log("log") // 传了参就用自己的装饰器参数
    static sub(x: number, y: number) {
        return x - y
    }
}

let v1 = M.add(1, 2);
console.log(v1);
let v2 = M.sub(1, 2);
console.log(v2);

什么是元数据?

元数据 :用来描述数据的数据,在我们的程序中,对象 等都是数据,它们描述了某种数据

另外还有一种数据,它可以用来描述 对象,这些用来描述数据的数据就是 元数据

比如一首歌曲本身就是一组数据,同时还有一组用来描述歌曲的歌手、格式、时长的数据,那么这组数据就是歌曲数据的元数据

使用 reflect-metadata

https://www.npmjs.com/package/reflect-metadata

首先,需要安装 reflect-metadata

npm install reflect-metadata

定义元数据

我们可以 方法 等数据定义元数据

  • 元数据会被附加到指定的 方法 等数据之上,但是又不会影响 方法 本身的代码
设置

Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey)

  • metadataKey:meta 数据的 key
  • metadataValue:meta 数据的 值
  • target:meta 数据附加的目标
  • propertyKey(可选):对应的 property key
获取

Reflect.getMetadata(metadataKey, target, propertyKey)

参数的含义与 defineMetadata 对应

import "reflect-metadata"

class A {
    public static method1() {
    }

    public method2() {
    }
}

let obj = new A;

Reflect.defineMetadata("n", 1, A);
Reflect.defineMetadata("n", 2, A, "method1");
Reflect.defineMetadata("n", 3, obj);
Reflect.defineMetadata("n", 4, A, "method2");

console.log(Reflect.getMetadata("n", A));
console.log(Reflect.getMetadata("n", A, "method1"))
console.log(Reflect.getMetadata("n", obj))
console.log(Reflect.getMetadata("n", obj, "method2"))
1
2
3
4
装饰器简化操作
  • 通过 Reflect.defineMetadata 方法调用来添加 元数据

  • 通过 @Reflect.metadata 装饰器来添加 元数据

import "reflect-metadata"

@Reflect.metadata("n", 1)
class A {
    @Reflect.metadata("n", 2)
    public static method1() {
    }


    @Reflect.metadata("n", 4)
    public method2() {
    }
}

let obj = new A;

console.log(Reflect.getMetadata("n", A));
console.log(Reflect.getMetadata("n", A, "method1"))
console.log(Reflect.getMetadata("n", obj))
console.log(Reflect.getMetadata("n", obj, "method2"))
1
2
undefined
4

使用元数据的 log 装饰器

import "reflect-metadata";

function L(type: string) {
    return function (target: Function) {
        Reflect.defineMetadata("type", type, target);
    }
}

function log(type?: string) {
    return function log(target: any, name: string, descriptor: PropertyDescriptor) {
        // log 方法装饰器比L类装饰器先执行,想访问L添加到原型链中的内容需要在

        let value = descriptor.value;
        descriptor.value = function (x: number, y: number) {
            let result = value(x, y);
            let _type = type;
            if (!_type) {
                if (typeof target === "function") {
                    _type = Reflect.getMetadata("type", target);
                } else {
                    // 实例
                    _type = Reflect.getMetadata("type", target.constructor)
                }
            }
            console.log({
                type: _type,
                name,
                x,
                y,
                result
            });
            return result;
        }
    }
}

@L("storage")
class M {
    @log()  // 没传参就用类装饰器的参数
    static add(x: number, y: number) {
        return x + y
    }
    @log("log") // 传了参就用自己的装饰器参数
    static sub(x: number, y: number) {
        return x - y
    }
}

let v1 = M.add(1, 2);
console.log(v1);
let v2 = M.sub(1, 2);
console.log(v2);

// 原始类
@L('log')
class M {
    @log
    static add(a: number, b: number) {
        return a + b;
    }
    @log
    static sub(a: number, b: number) {
        return a - b;
    }
}

let v1 = M.add(1, 2);
console.log(v1);
let v2 = M.sub(1, 2);
console.log(v2);

优化

import "reflect-metadata";

function log(type?: string) {
    return function log(target: any, name: string, descriptor: PropertyDescriptor) {
        // log 方法装饰器比L类装饰器先执行,想访问L添加到原型链中的内容需要在

        let value = descriptor.value;
        descriptor.value = function (x: number, y: number) {
            let result = value(x, y);
            let _type = type;
            if (!_type) {
                if (typeof target === "function") {
                    _type = Reflect.getMetadata("type", target);
                } else {
                    // 实例
                    _type = Reflect.getMetadata("type", target.constructor)
                }
            }
            console.log({
                type: _type,
                name,
                x,
                y,
                result
            });
            return result;
        }
    }
}

@Reflect.metadata("type", "storage")
class M {
    @log()  // 没传参就用类装饰器的参数
    static add(x: number, y: number) {
        return x + y
    }
    @log("log") // 传了参就用自己的装饰器参数
    static sub(x: number, y: number) {
        return x - y
    }
}

let v1 = M.add(1, 2);
console.log(v1);
let v2 = M.sub(1, 2);
console.log(v2);

使用 emitDecoratorMetadata

如何知道一个方法中有多少个参数,每个参数的类型是什么呢?

function f() {
    return function (target: any, name: string, descriptor: PropertyDescriptor) {
        console.log(descriptor.value.length);
    }
}

class B {
    name: string;
    constructor(a: string) {

    }
    @f()
    method(a: string, b: string): string {
        return "a";
    }
}
2

tsconfig.json 中有一个配置 emitDecoratorMetadata,开启该特性,typescript 会在编译之后自动给 方法访问符属性参数 添加如下几个元数据

  • design:type:被装饰目标的类型
    • 成员属性:属性的标注类型
    • 成员方法:Function 类型
  • design:paramtypes
    • 成员方法:方法形参列表的标注类型
    • 类:构造函数形参列表的标注类型
  • design:returntype
    • 成员方法:函数返回值的标注类型
import "reflect-metadata"

function n(target: any) {
}
function f(name: string) {
    return function(target: any, propertyKey: string, descriptor: any) {
      	console.log( 'design type', Reflect.getMetadata('design:type', target, propertyKey) );
        console.log( 'params type', Reflect.getMetadata('design:paramtypes', target, propertyKey) );
        console.log( 'return type', Reflect.getMetadata('design:returntype', target, propertyKey) );
    }
}
function m(target: any, propertyKey: string) {

}

@n
class B {
    @m
    name: string;

    constructor(a: string) {

    }

    @f('')
    method1(a: string, b: string) {
        return 'a'
    }
}

编译后

__decorate([
    m,
    __metadata("design:type", String)
], B.prototype, "name", void 0);
__decorate([
    f(''),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", void 0)
], B.prototype, "method1", null);
B = __decorate([
    n,
    __metadata("design:paramtypes", [String])
], B);

根据标注,返回对应内容

import "reflect-metadata";

function f() {
    return function (target: any, name: string, descriptor: PropertyDescriptor) {

        let _t = Reflect.getMetadata(
            "design:paramtypes",
            target,
            name
        )[0];
        console.log(_t);
        let value = descriptor.value;
        if (_t === Number) {
            console.log("标注的是个数字类型");
        }

        if (_t === String) {
            value("lc");
        }

        if (_t === Date) {
            value(new Date())
        }
    }
}

class B {
    name: string;
    constructor() {

    }
    @f()
    method(a: string, b: number): string {
        return "a";
    }

    @f()
    method2(x?: Date) {
        console.log(x)
    }
}

let b = new B();
b.method2()

在这里插入图片描述

    @f()
    method2(x?: number) {
        console.log(x)
    }

在这里插入图片描述

class B {
	...
    @f()
    method2(x?: string) {
        console.log(x)
    }
}

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值