ts的装饰器

ts的装饰器

ts的装饰器(Decorators)Spring注解(Annotation) 看起来很像。他们的设计理念都来自装饰器模式.

装饰器模式
在不改变对象自身结构的前提下,向对象添加新的功能

首先通过下面2种方式打开装饰器支持.

  1. 命令行开启
tsc --target ES5 --experimentalDecorators
  1. 配置tsconfig.json文件

执行tsc --init命令生成tsconfig.json配置文件后将experimentalDecorators设置为true

{
  "compilerOptions": {
    "target": "ES5",
     /* Enable experimental support for legacy experimental decorators. */
    "experimentalDecorators": true 
  }
}

1.装饰器

装饰器的本质是函数,通过@expression的方式使用。

装饰器分为5类:

装饰器种类
Class Decorators
Method Decorators
Accessor Decorators
Property Decorators
Parameter Decorators

虽然很想马不停蹄的介绍接下来几个装饰器,但是在此之前不得不先介绍一下装饰器工厂

1.1 装饰器工厂

装饰器工厂同样本质上也是1function.装饰器工厂返回的表达式,供装饰器在运行时调用.

function color(value: string) { // 这是一个装饰器工厂
    return function (target) { //  这是装饰器
        // do something with "target" and "value"...
    }
}

@color('blue')

看上去是不是和注解一模一样?在装饰器工厂变量target依据装饰器type的不同可能是

  1. the constructor function of the class for a static member
  2. the prototype of the class for an instance member.

装饰器工厂这样的写法用到了柯里化编程.

Currying是一种编程技术,它可以将一个固定数量参数的函数转换为一个链式调用的函数,允许其参数分阶段提供。例如,add(x,y,z) 会允许它像 add(x)(y)(z)add(x)(y,z) 等方式进行调用

// A curried function
let add = (x: number) => (y: number) => (z: number) => z + x + y;

// Simple usage
console.log(add(1)(4)(2));// Prints "7"

// partially applied
let add123 = add(123);
// fully apply the function
console.log(add123(456)(1000));// Prints "1579"

// 非箭头函数形式
function currying(x: number) {
    console.log('curry X');
    return function (y: number) {
        console.log('curry Y');
        return function (z: number) {
            console.log('curry Z');
            return x + z + y
        }
    }
}
console.log(currying(1)(2)(3));// Prints "6"

最后的展开形式看起来是不是跟装饰器工厂一模一样?

好了,终于到了介绍装饰器的环节😋

2. Class Decorators

类装饰器表达式会在运行时当作function被调用,类的constructor作为其唯一的参数

如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明.这也是需要extends的原因.

function decorator<T extends { new (...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    // 在这里可以添加新的属性或方法
  };
}

下面用一个详细的例子来说明吧,选自Class Decorators

function reportableClassDecorator<T extends { new(...args: any[]): {} }>(constructor: T) {
    console.log(constructor); // Prints "[class BugReport]"
    return class extends constructor {
        reportedUser = "J4ck";
        title = "Needs DARK mode"
    };
}
	
@reportableClassDecorator
class BugReport {
    type = "report";
    title: string;
	// reportedUser!: string			
    constructor(t: string) {
        this.title = t;
    }
}

const bug = new BugReport("Needs dark mode");
//只有在class definition中声明的属性才能直接调用
console.log(bug.type); // Prints "report"

// Note that the decorator _does not_ change the TypeScript type
// and so the new property `reportingURL` is not known
// to the type system:
//Property 'reportedUser' does not exist on type 'BugReport'.
console.log(bug.reportingURL);

/* decorator不会改变类型,因此为了能够调用有2种方法 */
/* 1.为该类添加该属性  */
/* 2.该类推导为any类型突破 _类型限制_  */
console.log((bug as any).reportedUser); // Prints "J4ck"

3. Method Decorators

TypeScript中,方法装饰器是一种特殊类型的声明,它能够被附加到类的方法上。方法装饰器表达式会在运行时当作函数被调用,需要传入以下3个参数

参数类型是什么
targetany对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
propertyKeystring方法名
descriptorPropertyDescriptor成员的属性描述符

我觉得这个例子非常好,还能顺便聊一聊装饰器的执行顺序

  1. @expression(arg: type)先从外层到内层(如果是函数工厂的话)
  2. @expression时,是从内层到外层
function fn(str: string) {
    console.log("求值装饰器:", str);
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("应用装饰器:", str);
    };
}

function decorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("应用装饰器: decorator");
}

class T {
    @fn("first")
    @decorator
    @fn("second")
    method() { }
}
/*
求值装饰器: first
求值装饰器: second
应用装饰器: second
应用装饰器: decorator
应用装饰器: first
*/

4. Property Decorators

属性装饰器能够被附加到类的property .属性装饰器表达式会在运行时当作函数被调用,需要传入以下2个参数:

参数类型是什么
targetany对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
propertyKeystring属性名

请注意,属性装饰器不能直接修改属性的值,修改的是原型中的值

import "reflect-metadata"; // npm install reflect-metadata

const formatMetadataKey = Symbol("format");

function format(formatString: string) {
    return Reflect.metadata(formatMetadataKey, formatString);
}
function getFormat(target: any, propertyKey: string) {
    return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

class Greeter {
    @format("Hello, %s")
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        let formatString = getFormat(this, "greeting");
        return formatString.replace("%s", this.greeting);
    }
}

let greet = new Greeter('J4ck')
console.log(greet.greet());	//	Prints: Hello, J4ck

5. Parameter Decorators

参数装饰器能够被附加到类的property .参数装饰器表达式会在运行时当作函数被调用,需要传入以下3个参数:

参数类型是什么
targetany对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
propertyKeystring方法名
parameterIndexnumber参数在函数参数列表中的索引

参数装饰器属性装饰器很像,因为装饰器在类声明后就已经确定并调用了,而属性需要实例化后才能确定,参数则是需要在function调用时才能确定.

function logParameter(target: any, propertyKey: string, parameterIndex: number) {
    console.log(`Parameter ${parameterIndex} of method ${propertyKey} of class ${target.constructor.name} was accessed.`);
}

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }

    setGreeting(@logParameter greeting: string) {
        this.greeting = greeting
    }
}
// Prints "Parameter 0 of method setGreeting of class Greeter was accessed."

同样是1个很好的例子,我稍微将其改了1下,来自于一起读透TS装饰器#小试牛刀:方法参数类型校验.不过这个小例子使用了Metadata,如果不熟悉的可以到第6章了解1下然后自行学习.

type Validator = (value: unknown) => boolean;

const isString = applyValidator((x) => typeof x === "string");
const isNumber = applyValidator((x) => typeof x === "number");

class Person {
    @validate
    saySomething(@isString a: any, @isNumber b: any) {
        console.log(`a: ${a} || b:  ${b}`);
    }
}

function applyValidator(validator: Validator) {
    return function (target: any, key: string, idx: number) {
        let validators: Validator[] = [];
        // validatorMap 用于收集不同方法参数校验器
        let validatorMap: Map<string, Validator[]> = Reflect.getMetadata(key, target)
        if (validatorMap) {
            if (validatorMap.has(key)) {
                //validatorMap && key 都存在
                validators = (validatorMap.get(key) as Validator[]);
            }
        } else {
            // validatorMap不存在_new_1个
            validatorMap = new Map<string, Validator[]>();
        }
        /*
        1. validatorMap不存在,刚new所以key不存在
        2. validatorMap存在,但该key不存在
        将新的检验器加入到数组中,数组第几项就对应第几个参数的校验器
        出于简化目的,假设每一个参数最多只能有一个校验器
         */
        validators[idx] = validator;
        validatorMap.set(key, validators);
        //将validatorMap通过defineMetadata重新赋值回去
        Reflect.defineMetadata(key, validatorMap, target);
    };
}

function validate(target: any, key: string, descriptor: PropertyDescriptor) {
    const origin = descriptor.value;
    descriptor.value = function (...args: unknown[]) {
        // 获取validatorMap元数据
        let validatorMap: Map<string, Validator[]> = Reflect.getMetadata(key, target)
        //   如果该方法不需要校验,则直接运行原方法
        if (!validatorMap.has(key)) {
            return origin.apply(this, args);
        }
        let validators = validatorMap.get(key);
        //先对方法的每一个参数进行校验,遇到不符合规则的情况,直接报错
        if (validators) {
            validators.forEach((validator, idx) => {
                if (!validate) {
                    return;
                }
                if (!validator(args[idx])) {
                    throw new TypeError(`Type validate failed for parameterIndex\[${idx}\]:${args[idx]}`);
                }
            });
        }
        // 所有校验通过后再运行原方法
        return origin.apply(this, args);
    };
}


// Prints  "a: str || b:  12"
new Person().saySomething("str", 12); 

// Prints	"Type validate failed for parameterIndex[0]:12"
new Person().saySomething(12, 12);

// Prints	"Type validate failed for parameterIndex[1]:other str"
new Person().saySomething("str", "other str");

6. Metadata

Metadata

  1. 命令行下载
npm install reflect-metadata
  1. 开启Metadata支持

命令行开启

tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata

配置tsconfig.json开启

{
  "compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Metadata的项目地址👉reflect-metadata


TypeScript Reference Decorators

TypeScript手册指南装饰器

一起读透TS装饰器

柯里化 | 深入理解 TypeScript

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值