TS装饰器的使用以及应用(基于express实现Controller等装饰器)

1,295 阅读7分钟

TS

TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类	型和基于类的面向对象编程。
TypeScript 扩展了 JavaScript 的句法,所以任何现有的 JavaScript 程序可以不加改变的在 TypeScript 下工作。	TypeScript 是为大型应用之开发而设计,而编译时它产生 JavaScript 以确保兼容性。
这段是我在网上扒下来的

为什么要用TS对express进行封装呢?

主要是为了用ts里面的装饰器,虽然装饰器已经是es7里面的一个提案了,但是在js里面
使用的话还需要用babel进行配置(太麻烦了,图省事所以用Ts)

我们先来了解一下什么是装饰器

装饰器(Decorator) 仅提供定义劫持,能够对类及其方法、方法入参、属性的定义
并没有提供任何附加元数据的功能。
主要是为了提供附加的功能

那么TS里面的装饰器如何使用呢

二话不说npm一把梭
npm init -y //使用默认配置
npm i typescript -S //安装ts包
tsc --init //初始化ts配置
mkdir src //创建src目录
mkdir decorators //创建装饰器目录
touch common.ts //创建common 用于辨析而装饰器
touch index.ts //创建index文件
到此为止已经搭建了项目的基本架构

接下来我们介绍一下TS里面有那些类型的装饰器

类装饰器

应用于类的构造函数
参数:target (类)
写法:function classDecorators(target:any){
	//我们在这个地方来对象进行操作
    //比我我们给类里面的原型加一点东西
    target.prototype.usernmae='沉默'
}

属性装饰器

应用于属性上面的装饰器
参数:targte (类),name(当前属性的名字)
写法:
function attrDecorators(target:any,name:string){
    target[name]=150 //我们给当前属性赋了一个值
}

方法装饰器

应用于方法上面的装饰器
参数:targte (类),name(当前方法的名字),descriptor (当前方法的描述对象)
写法:
function funcDecorators(target:any,name:string,descriptor:PropertyDescriptor){
    //我们在这个装饰器里面来写一些,在每次执行的时候打印一个时间
    let origin=descriptor.value;//这个就是当前方法的内容,我们给他保存起来
    descriptor.value=function(){
        console.log(new Date())
        //执行我们保存的那个方法 arguments是传进来的那些参数
        return origin.apply(this,arguments)
    }
    return descriptor
}

方法参数的装饰器

应用于方法参数上面的装饰器
参数:target(类),name(当前参数方法的名称),index(按当前参数的在形参列表里面的索引值)
function paramDecorators(target:any,name:string,index:number){
	//这个装饰器也没有上面例子好举了
}

这是我们讲的ts的装饰器,我们一起加在一个类上面,来看看他的执行顺序是上面样的

我们可以看到先走的是属性的装饰器,之后是参数在是方法然后是类

我看别人的装饰器里面都能传值,为什么我看你这个不能呢!

不要着急,马上就会讲到,TS里面还有一个叫装饰器工厂的装饰器就是用来完成这个工作的。
装饰器工厂的定义方法就是返回的是一个方法
来看例子
function classDesc(path:string){
  //这就是一个装饰器工厂,上面的path就是你可以传进来的值
  console.log(path,'class')
  return (target:any)=>{
      console.log(typeof target,'class')
  }
}
其他类型的装饰器的写法也都类似与这样,只不过是参数不一样

接下来我们讲讲js里面的元数据

元数据即为描述数据的数据,也有个称呼叫
注解(Annotation) 仅提供附加元数据支持,并不能实现任何操作。需要另外的 Scanner 根据元数据执行相应操作。
但是我一听到注解就像Java里面的东西
总而言之,装饰器和注解是不一样的,我上面有讲过装饰器的概念,主要是为了提供附加功能,注解不提供这些,它只提供元数据的支持,不能操作任何东西,所以只能根据元数据来实现具体的操作。

知道元数据之后我们就可以来封装标题上面所讲的装饰器了,封装那些需要用到

我们最终会是实现这些装饰器
Controller Get Post Body Query 等
之前我们以及创建了common.ts文件,那么就在这里先创建第一个装饰器(Controller)
const PATH_METADETA='path';
const PATH_METADETA = "path";
const PARAMS_METADETA = "params";
function Controller(path:string):ClassDecorator{
	//创建装饰器工厂,可以用来接收参数,上面以及讲过了
  return (target)=>{
  	/*
      给当前类添加一个元数据
      Reflect.defineMetadata用来添加元数据,总共有四个值第一个是key,唯一的,
      第二个是值,第三个是当前对象,第四个是方法名,但我们当前只给类设置元数据,就不需要
      填方法
      为什么要设置元数据呢,装饰器本来就是用来扩展方法的,但是他是用来对本方法进行扩展的,
      在此装饰器里面我们拿不到,具体的数据,所以我们用元数据去设置具体的数据,在其他地方进行
      通用的注册使用,这个装饰器的作用就是用设置元数据的(我也不知道这样讲能不能明白:))
      */
  	Reflect.defineMetadata(PATH_METADETA, path, target);//当前装饰器传过来
      //的数据给设置进入了类的元数据里面了,日常的时候这个类并不影响我们的使用
  }
}
//我们需要编写Post Get 这些的方法,本质上这些方法除了Post,Get这些不一样,传过来的东西都
//是一样的
function createdMethod(methods: string) {
  return (path: string): MethodDecorator => {
    return (target: any, key: string | symbol, descriptor: any) => {
      //给当前方法设置了一个
      Reflect.defineMetadata(PATH_METADETA, path, target, key);
      //添加方法元数据,描述当前方法为什么方法
      Reflect.defineMetadata(METHOD_METADETA, methods, target, key);
    };
  };
}
目前我们先写这两个装饰器,之后在写params的装饰器

装饰器虽然是写好了

但是我们在那里去用呢,不会这么简单吧。
当然不会那么简单,我们现在还需要一个注册的地方,我们需要让express拿到装饰器装饰上去的
元数据。

我们在decorators里面在建一个文件 叫	register.ts 用来注册我们装饰的类
下面是代码
import {Router} from 'express';
function register(app:any,controller:Array<any>){
	const router=Router();
 	controller.forEach((v:any)=>{
         //这个v就是类
         //获取当前类key为path的元数据 也就是之前我们装饰器设置的值
         let classPath=Reflect.getMetadata('path',v);
         //遍历当前属性,查询当前类属性上面有哪些元数据
         for(const key in new v()){
         	//这个getMetadata里面的key是当前遍历的方法的名称
             //也就拿到当前方法设置的元数据
             let attrMethod = Reflect.getMetadata('method', new v(), key);
             let attrPath = Reflect.getMetadata('path', new v(), key);
             //我们在定义一个回调方法,这个就是express的回调方法
             let fn:any=(req,res,next)=>{
             	//我们先把我们当前的方法给执行一下,在传两个参数(这个方法就是@Post@Get这个中下面的方法)
               let result=new v()[key].apply(new v(),[req,res]);
               if (result instanceof Promise) {
               	result.then(value => {
               		!res.headersSent && res.send(value);
               	}).catch(err => {
               		next(err)
               	})
               } else if (result !== undefined) {
               		!res.headersSent && res.send(result);
               }
                 
             }
             //组装一下路由的路径
             let routerParams = [classPath + attrPath]
             //在添加回调
             routerParams.push(fn)
             //去调用router方法
             router[attrMethod].apply(router, routerParams)
         }
         //用来解决body
         app.use(bodyParser.json())
         app.use(bodyParser.urlencoded({ extended: false }))
         app.use(router)//使用路由
 	})
}
在index.ts 里面启动express并注册,我们的装饰器应该就能和router联动起来了
  register(app,[UserController]);

  var server = app.listen(3122, function () {
      var host = server.address().address;
      var port = server.address().port;
      console.log('Example app listening at http://%s:%s', host, port);
  })
自此也就算是写完了!我也是研究了一下午,看了github上面的一些主流的框架的源码,剥离出来的简单的版本

我也是一个没干多久小菜鸟,希望这个篇文]章对你们有所帮助
我自己写的那个项目在[github](https://github.com/mrzhouxl/express-decorator)