TS 装饰器 Decorators 还在更新中.....

1,647 阅读5分钟

装饰器基本了解

  1. 装饰器的目的就是为了给代码添加新的功能,随着程序的功能越来越多,需要给某一个小块添加上功能,这时可以使用装饰器,对类进行方法的新增操作,装饰器与ES6 中的面向对象的继承式有一定的区别的,但是本质上,他们都是在操作原型对象,通过给原型对象 prototype 添加一些方法和属性,来扩展类的功能
  2. 装饰器就是装饰类的,类自身有很多的功能,我们也可以通过装饰器,在额外的给类添加新的功能
  3. 装饰器不仅仅可以操作类,也可以对类自身的方法进行重写

环境的搭建

  1. 需要初始化一个 TS 的配置文件,并且侦听 TS 文件的编译运行,通过 tsc --init 去初始化一个装饰器
  2. 运行 TS, tsc -w 执行编译
  3. 在配置文件中找到以下两项,并且将他进行开启
"experimentalDecorators": true,               
"emitDecoratorMetadata": true,  

装饰器类别

名称装饰器作用
ClassDecorator类(class)的装饰器
MethodDecorator方法装饰器
PropertyDecorator属性装饰器
ParameterDecorator参数装饰器

类装饰器

类的装饰器

装饰器的定义,装饰器的本身,他是一个函数,会在运行的时候被调用,被装饰的类,会作为参数传递给装饰器函数,当作形参。

类装饰器接收一个构造函数作为参数,参数的类型是一个函数

const moveDecortor:ClassDecorator = (target:Function):any => {
  console.log(target);
}
@moveDecortor
class User {
}

装饰器的使用后方式,定义完成装饰器,通过 @ + 装饰器名称,将他书写在要装饰的类的前面,这样的含义就是,当前装饰器下面的类,会一参数的形式,传递给装饰器函数

{
  
  const moveDecortor:ClassDecorator = (target:Function):any => {
    target.prototype.forName = ():number => {
      return 1
    }
  }

  @moveDecortor
  class User{} 1.  可以在类中定义同名方法,消除报错
  const u = new User()
  console.log((<any>u).forName());
}

装饰器的叠加

装饰器不仅仅只能使用一个,可以定义多个装饰器,作用于一个类身上,通过叠加装饰器的方式,给类追加多个方法和属性

{
  
  const moveDecortor:ClassDecorator = (target:Function):any => {
    target.prototype.forName = ():number => {
      return 1
    }
  }

  const musicDecortor: ClassDecorator = (target:Function):any => {
    target.prototype.block = ():void => {
      console.log('播放音乐');
    }
  }

  @moveDecortor
  @musicDecortor
  class User{
    public forName() {}
    public block() {}
  }
  const u = new User()
  console.log((u.forName())
  u.block()
}

装饰器工厂

装饰器工厂的作用就是,根据使用装饰器的类,类给装饰器工程传递数据,根据数据返回不同的装饰器,这个函数被称之为 装饰器工厂

{
  const musicDecoratorFactory = (str: string):ClassDecorator => {
    switch (str) {
      case 'Token':
      return (target: Function):any => {
        target.prototype.play = function():void {
          console.log('播放音乐' + str);
        }
      };
      default:
      return (target: Function):any => {
        target.prototype.play = function():void {
          console.log('播放音乐' + str);
        }
      };
    }
  }
  
  @musicDecoratorFactory('Token')
  class Music {
    public play() {}
  }

  new Music().play()

}

方法装饰器

方法装饰器与类装饰器的使用方式相同,但是装饰器作用的位置式不同的,方法装饰器需要用在类方法的前面,将方法作为参数,传递给装饰器

const showDecorator:MethodDecorator = ( ...args:any[] ):any => {
  console.log(args)
}

class User {
  @showDecorator
  public show () {}
}

args的打印结果
[
   1. 如果是静态方法,这里就是构造函数,如果是普通方法就是原型对象
   { show: [Function (anonymous)] }, 
   'show', 2. 函数名
   {  3. 该函数可以被执行的操作,是否可读写,可复制
     value: [Function (anonymous)], 
     writable: true,
     enumerable: true,
     configurable: true
   }
 ]

不使用数组接收参数,这样的好处就是,在操作接收的参数的时候,不需要通过数组下标的方式去找到,要操作的对象,直接调用,代码可阅读性的提高

const showDecorator:MethodDecorator = ( target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor ):any => {
  console.log(args)
}

class User {
  @showDecorator
  public show () {}
}

延时器在装饰器里面使用 & 装饰器工厂

{
  const setTimeoutDecorator:MethodDecorator = (...args:any[]):any => {
    const [,,descriptor] = args;
    const msg = descriptor.value;

    descriptor.value = () =>{
      setTimeout(() => {
        msg()
      },3000)
    }
  }
  class SetTime {
    
    @setTimeoutDecorator
    public times() {
      console.log('延时加载');
    }

  }
  new SetTime().times();
}

这里需要注意的事项就是,在装饰器里,使用的技巧就是先将原有的值进行存储一下,在去使用,确保它使用的是类中的方法中的值

全局错误处理

  1. 通过使用装饰器可以对 方法抛出的错误,将错误提示通过装饰器进行重写,也就是字定义错误信息
{
  const ErrorDecorator:MethodDecorator = (...args: any[]) => {
    const [,,descriptor] = args;
    const method = descriptor.value
    descriptor.value = () =>{
      try {
        method()
      } catch (error) {
        console.log('这是新定义的错误')
      }    
    }
  }

  class User {
    @ErrorDecorator
    public errorMsg () {
      throw new Error('报错了')
    }

  }
  new User().errorMsg()

}

属性装饰器与方法装饰器

属性装饰器的使用

{
  const nameDecorator:PropertyDecorator = (target: Object, propertyKey: string | symbol) => {
    Object.defineProperty(target,propertyKey, {
      set (v) {
        console.log(v);
        
      } 
    })
  }
  class User {
    @nameDecorator
    public  str: string;
    constructor(str: string) {
      this.str = str
    }
  }
  console.log(new User('小明'));
}

属性装饰器的参数

[ {}, 'str', undefined ]
1. str 属性名称
2. {} 原型链 || 构造函数

方法参数访问器

{
  const nameDecorator:ParameterDecorator = (...args: any[]) => {
    console.log(args);
  }
  
  class User {
    public getName(num:number,@nameDecorator str: string) {
      console.log(num,str);
    }
  }

  new User().getName(1,'小明')
}

参数的介绍

target: Object, propertyKey: string | symbol, parameterIndex: number
[ { getName: [Function (anonymous)] }, 'getName', 0 ]

第一个代表的是当前方法
第二个代表的是方法名称
第三个代表的是,当前参数在整个方法参数中,所在的位置

元数据

首先来看普通的数据创建

let U = {
    name: '普通数据'
}

元数据是普通数据的数据,我们可以给 name 属性额外的添加一些描述,这类数据被称为 元数据,在 TS 中默认是没有元数据的,需要通过 npm 下载相关包文件

npm init -y
npm i reflect-metadata --save

使用元数据

  1. 使用【Reflect.defineMetadata】创建一个元数据
    • 参数一:元数据名称
    • 参数二:元数据的值
    • 参数三:要被设置元数据信息的对象
    • 参数四:对象内部哪一个属性要设置元数据,属性名称
import 'reflect-metadata'

{
  let u = {
    name: '元数据'
  }

  Reflect.defineMetadata('md',{sex: '男'},u, 'name')
  console.log(Reflect.getMetadata('md',u,'name'));
  
}
  1. 通过 【Reflect.getMetadata】获取元信息
    • 元数据名称
    • 哪一个对象,对象里面哪一个属性

元数据结合参数装饰器验证参数(待更新)