聊聊Typescript中的设想形式——装潢器篇(decorators)

  跟着Typescript的提高,在KOA2和nestjs等nodejs框架中常常看到类似于java spring中注解的写法。本文从装潢形式动身,聊聊Typescipt中的装潢器和注解。

  • 什么是装潢者形式
  • Typescript中的装潢器
  • Typescript中的注解
  • 总结

原文地点:https://github.com/forthealll…

迎接star

一、什么是装潢者形式

  近来在看nestjs等支撑Typescript的node框架,常常看到如许一种写法:

import { Controller, Get } from '@nestjs/common';

@Controller('cats')

export class CatsController {
  @Get()
  findAll() {
    return 'This action returns all cats';
  }
}

  上述代码定义了一个处置惩罚url为“/cats”的控制器,该控制器关于url为“/cats”的get要领实行findAll()函数,返回响应的字符串。

  在上述的代码中,用@Controller(‘cats’)润饰CatsController类,经由过程@Get来润饰类中的findAll要领,这就是典范的装潢者形式。经由过程@Controller(‘cats’)和@Get润饰后的类CatsController,简朴来讲,就是具有了雄厚的“内在”。

下面看看详细装潢者形式的定义:

我们晓得继续形式是雄厚子元素“内在”的一种主要体式格局,不管是继续接口照样子类继续基类。而装潢者形式能够在不转变继续关联的前提下,包装先有的模块,使其内在越发雄厚,并不会影响到本来的功用。与继续比拟,越发的天真。

javascript中的装潢器处于发起征集的第二阶段,经由过程babel和Typescrit都能够完成装潢器的语法。

二、Typescript中的装潢器

Typescript中的装潢器与类相干,离别能够润饰类的实例函数和静态函数、类自身、类的属性、类中函数的参数以及类的set/get存取器,下面来意义引见。

(1)、类要领的装潢器

下面来引见一下用装潢器来润饰函数,起首来看一个例子:


let temple;
function log(target, key, descriptor) {
  console.log(`${key} was called!`);
  temple = target;
}
class P {
    @log
    foo() {
      console.log('Do something');
    }
}

const p = new P()
p.foo()
console.log(P.prototype === temple) //true

上述是实例要领foo中我们用log函数润饰,log函数接收三个参数,经由过程P.prototype === temple(target)能够推断,在类的实例函数的装潢器函数第一个参数为类的原型,第二个参数为函数名自身,第三个参数为该函数的形貌属性。

详细总结以下,关于类的函数的装潢器函数,顺次接收的参数为:

  • target:假如润饰的是类的实例函数,那末target就是类的原型。假如润饰的是类的静态函数,那末target就是类自身。
  • key: 该函数的函数名。
  • descriptor:该函数的形貌属性,比方 configurable、value、enumerable等。

从上述的例子中我们能够看到,用装潢器来润饰响应的类的函数非常轻易:

@log
foo() {
  ...
}

(2)、类的装潢器

装潢函数也能够直接润饰类:


let temple
function foo(target){
   console.log(target);
   temple = target
}
@foo
class P{
   constructor(){
     
   }
}

const p = new P();
temple === P //true

当装潢函数直接润饰类的时刻,装潢函数接收唯一的参数,这个参数就是该被润饰类自身。上述的例子中,输出的target就是类P的自身。

另外,在润饰类的时刻,假如装潢函数有返回值,该返回值会从新定义这个类,也就是说当装潢函数有返回值时,实际上是生成了一个新类,该新类经由过程返回值来定义。

举例来讲:

function foo(target){
   return class extends target{
      name = 'Jony';
      sayHello(){
         console.log("Hello "+ this.name)
      }
   }
}
@foo
class P{
   constructor(){
     
   }
}

const p = new P();
p.sayHello(); // 会输出Hello Jony

上面的例子能够看到,当装潢函数foo有返回值时,实际上P类已被返回值所代表的新类所替代,因而P的实例p具有sayHello要领。

(3)、类的属性的装潢器

下面我们来看类的属性的装潢器,装潢函数润饰类的属性时,在类实例化的时刻挪用属性的装潢函数,举例来讲:

function foo(target,name){
   console.log("target is",target);
   console.log("name is",name)
}
class P{
   @foo
   name = 'Jony'
}
const p = new P();
//会顺次输出 target is f P()  name is Jony

这里关于类的属性的装潢器函数接收两个参数,关于静态属性而言,第一个参数是类自身,关于实例属性而言,第一个参数是类的原型,第二个参数是指属性的名字。

(4)、类函数参数的装潢器

接着来看类函数参数的装潢器,类函数的参数装潢器能够润饰类的构建函数中的参数,以及类中其他一般函数中的参数。该装潢器在类的要领被挪用的时刻实行,下面来看实例:

function foo(target,key,index){
   console.log("target is",target);
   console.log("key is",key);
   console.log("index is",index)
}
class P{
   test(@foo a){
   }
}
const p = new P();
p.test("Hello Jony")
// 顺次输出 f P() , test , 0 

类函数参数的装潢器函数接收三个参数,顺次为类自身,类中该被润饰的函数自身,以及被润饰的参数在参数列表中的索引值。上述的例子中,会顺次输出 f P() 、test和0。再次明白一下润饰函数参数的装潢器函数中的参数寄义:

  • target: 类自身
  • key:该参数地点的函数的函数名
  • index: 该参数在函数参数列表中的索引值

从上面的Typescrit中在基类中经常运用的装潢器后,我们发明:

装潢器能够起到星散庞杂逻辑的功用,且运用上极为简朴轻易。与继续比拟,也越发天真,能够从装潢类,到装潢类函数的参数,能够说武装到了“牙齿”。

三、Typescript中的注解

在了解了Typescrit中的装潢器以后,接着我们来看Typescrit中的注解。

什么是注解,所谓注解的定义就是:

为响应的类附加元数据支撑

所谓元数据能够简朴的诠释,就是润饰数据的数据,比方一个人有name,age等数据属性,那末name和age这些字段就是为了润饰数据的数据,能够简朴的称为元数据。

元数据简朴来讲就是能够润饰某些数据的字段。下面给出装潢器和注解的诠释和区分:

  • 装潢器:定义挟制,能够对类,类的要领,类的属性以及类的要领的入参举行修正。不供应元数据的支撑。
  • 注解:仅供应元数据的支撑。

两者之间的联络:

经由过程注解添加元数据,然后在装潢器中猎取这些元数据,完成对类、类的要领等等的修正,能够在装潢器中添加元数据的支撑,比方能够能够在装潢器工场函数以及装潢器函数中添加元数据支撑等

(1)、Typescript中的元数据操纵

能够经由过程reflect-metadata包来完成关于元数据的操纵。起首我们来看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);
}

这里的format能够作为装潢器函数的工场函数,由于format函数返回的是一个装潢器函数,上述的要领定义了元数据Sysmbol(“format”),用Sysmbol的缘由是为了防备元数据中的字段反复,而format定义了取元数据中响应字段的功用。

接着我们来在类中运用响应的元数据:

class Greeter {
    @format("Hello, %s")
    name: string;

    constructor(name: string) {
        this.name = message;
    }
    sayHello() {
        let formatString = getFormat(this, "name");
        return formatString.replace("%s", this.name);
    }
}

const g = new Greeter("Jony");
console.log(g.sayHello());

在上述中,我们在name属性的装潢器工场函数,实行@format(“Hello, %s”),返回一个装潢器函数,且该装潢器函数润饰了Greeter类的name属性,将“name”属性的值写入为”Hello, %s”。

然后再sayHello要领中,经由过程getFormat(this,”name”)取到formatString为“Hello,%s”.

四、总结

经由过程装潢器,能够轻易的润饰类,以及类的要领,类的属性等,比拟于继续而言越发天真,另外,经由过程注解的要领,能够在Typescript中引入元数据,完成元编程等。特别是在angularjs、nestjs中,大批运用了注解,特别是nestjs构建了类似于java springMVC式的web框架。

    原文作者:yuxiaoliang
    原文地址: https://segmentfault.com/a/1190000017397209
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞