Typescript学习笔记(21) ----- 装饰器(Decorator)

什么是装饰器

装饰器本质上一个函数

类的装饰器

  • 对类进行装饰
开启装饰器语法:

tsconfig.json文件中,将这两项开启:

/* Experimental Options */
"experimentalDecorators": true, 
"emitDecoratorMetadata": true,
  • 装饰器的基本语法,使用@进行使用
function TestDecorator(constructor:any){
  console.log('test decorator')
}

@TestDecorator
class Test{
  
}

const test = new Test()
// 输出 test decorator
  • 类的装饰器是针对类而言的,不是针对实例而言的,所以只会执行一次
function TestDecorator(constructor:any){
  console.log('test decorator')
}

@TestDecorator
class Test{
  
}

const test = new Test()
const test2 = new Test()
// test decorator
  • 可以使用工厂模式对装饰器进行包装
    使用场景:按条件添加装饰器
function TestDecoratorFactory(flag:boolean){
  if(flag){
    return function(constructor:any){
      console.log('test decorator')
    }
  }else{
    return function(constructor:any){}
  }
}

@TestDecoratorFactory(true)
class Test{
  
}

const test = new Test()
// test decorator
使用装饰器更改类的构造函数

使用装饰器,为类增加一个getName的方法

function TestDecoratorFactory(flag:boolean){
  if(flag){
    return function(constructor:any){
      //直接绑到prototype之上
      constructor.prototype.getName = () =>{
        return 'yyc'
      }
    }
  }else{
    return function(constructor:any){}
  }
}

@TestDecoratorFactory(true)
class Test{
  
}
// 先暂时any处理,后面会提到
const test:any = new Test()
console.log('name is',test.getName()) 
//yyc

虽然完成了,但是ts并不知道对类挂载了getName这个方法,所以在编译时会提示错误,需要先指出构造函数的类型。

  • 有一点需要强调,最后返回出来的还是一个函数(继承了构造函数
// T是一个被实例化出来的构造函数
function TestDecorator<T extends new (...args:any[]) => any>(constructor:T){
//对原有装饰器的补充
  return class extends constructor{
    name = 'yyc'
    getName(){
        return this.name
      }
  }
}

@TestDecorator
class Test{
  name:string;
  constructor(name:string){
    this.name = name
  }
}

const test:any = new Test('cyy')
console.log('name is',test.getName()) 
// yyc

这样还是不能让ts推断出实例化的类的类型,使用工厂模式来解决这一问题

  • 使用工厂模式返回一个装饰器
  • 装饰器对基础类装饰后返回一个新的类
function TestDecoratorFactory(){
  return function<T extends new (...args:any[]) => any>(constructor:T){
    return class extends constructor{
      name = 'yyc'
      getName(){
        return this.name
      }
    }
  }
}

const Test = TestDecoratorFactory()(
  class {
    name:string;
    constructor(name:string){
      this.name = name
    }
  }
)
const test = new Test('cyy')
console.log('name is',test.getName()) 
//name is yyc

类中方法的装饰器

方法的装饰器,语法上和类的装饰器大致相同,但是接受参数有些区别

  • target:原型
  • key:方法名
  • description:对方法的定义,与define
function getNameDecorator(target:any, key:string){
  console.log('target is',target,'key',key)
}

class Test{
  name:string;
  constructor(name:string){
    this.name = name
  }
  @getNameDecorator
  getName(){
    return this.name
  }
}

const test = new Test('cyy')
//out put:
// target is Test { getName: [Function] } key getName
  • 如果装饰的是静态方法:
function getNameDecorator(target:any,key:string){
  console.log('target is',target,'key',key)
}

class Test{
  name:string;
  constructor(name:string){
    this.name = name
  }
  @getNameDecorator
  static getName(){
    return name
  }
}
//output
target is [Function: Test] { getName: [Function] } key getName
  • 第三个参数description,与Object.defineProperty()基本一致,可以对方法是否重写,以及方法默认值进行定义:
    其余的属性查阅文档
    • description.writable = false 不可重写
function getNameDecorator(target:any,key:string,description:PropertyDescriptor){
  console.log('target is',target,'key',key)
//不可重写
  description.writable = false
}

class Test{
  name:string;
  constructor(name:string){
    this.name = name
  }
  @getNameDecorator
  getName(){
    return this.name
  }
}

const test = new Test('cyy')
test.getName = () => {
  return 'aaa'
}
console.log(test.getName())
//会抛出错误:Cannot assign to read only property 'getName' of object '#<Test>'

访问器的装饰器

  • 访问器的装饰器和方法的装饰器大致相同
  • gettersetter不能同时具备装饰器
  • 通过改写装饰器,可以使访问器失效,比如description.writable = false
function nameDecorator(target:any,key:string,description:PropertyDescriptor){

}

class Test{
  private _name:string;
  constructor(name:string){
    this._name = name
  }
  get name(){
    return this._name
  }
  @nameDecorator
  set name(name:string){
    this._name = name
  }
}

const test = new Test('yyc')
test.name = 'yyc again'
console.log(test.name)

属性的装饰器

类的属性同样也可以配备装饰器

  • 只接受两个参数:
    • target:原型
    • key :属性名
  • 可以通过返回一个decriptor对属性进行设置
function NameDecorator(target:any,key:string):any{
  const decorator:PropertyDescriptor = {
    writable:false
  }
  return decorator
}

 class Test{
   @NameDecorator
   name = 'yyc'
 }

 const test = new Test()
 test.name = 'yyc2'
//output:Cannot assign to read only property 'name' of object '#<Test>'
  • 不能通过装饰器对属性值进行更改,因为更改的是原型上的而不是实例上的。
function NameDecorator(target:any,key:string):any{
  target[key] = 'yyc2'
}

 class Test{
   @NameDecorator
   name = 'yyc'
 }

 const test = new Test()
console.log('test',test.name,(test as any).__proto__.name)
//test yyc yyc2

参数的装饰器

  • 参数的装饰器一共接受三个参数:
    • 原型:target
    • 方法名
    • 装饰器装饰的参数的位置
function ParamDecorator(target:any,key:string,paramIndex:number){
  console.log('target',target,'key',key,'paramIndex',paramIndex)
//target Test { getInfo: [Function] } key getInfo paramIndex 0
}

class Test{
  getInfo(@ParamDecorator name:string,age:number){

  }
}
const test = new Test()
test.getInfo('yyc',30)

装饰器的实际应用

使用装饰器对异常统一进行处理
const userInfo:any = undefined

function ErrorDecorator(target:any,key:string,descriptor:PropertyDescriptor){
  //fn是对函数的引用
  const fn = descriptor.value
  //对调用的函数进行覆写
  descriptor.value = function(){
    try{
      fn()
    }catch(e){
      console.log('error happen')
    }
  }
}

class Test{
  @ErrorDecorator
  getName(){
    return userInfo.name
  }
  getAge(){
    return userInfo.age
  }
}


const test = new Test()
test.getName()
//output:error happen
  • 更进一步,需要自定义抛出错误的消息
    使用工厂模式:
function ErrorDecoratorFactory(errMsg:string){
  return function(target:any,key:string,descriptor:PropertyDescriptor){
    const fn = descriptor.value
    descriptor.value = function(){
      try{
        fn()
      }catch(e){
        console.log(errMsg)
      }
    }
  }
}
class Test{
  //传递参数
  @ErrorDecoratorFactory('invalid name')
  getName(){
    return userInfo.name
  }
  @ErrorDecoratorFactory('invalid age')
  getAge(){
    return userInfo.age
  }
}
//invalid name
test.getName()
//invalid age
test.getAge()

metadata 原数据的定义和反射

需要将tsconfig.json这么设置:

"module": "commonjs", 

才可以使用reflect metadata

  • 利用Reflect检查装饰器使用的时机
    是先调用方法上的装饰器,最后调用类上的装饰器
function showData(target:typeof User){
  for(let i in target.prototype){
    const func = Reflect.getMetadata('data',target.prototype,i)
    console.log('func',func)
  }
}

function setData(target:any,key:string,description:any){
  Reflect.defineMetadata('data',key,target,key)
}

function setDataFactory(value:string){
  return function(target:any,key:string,description:any){
    Reflect.defineMetadata('data',value,target,key)
  }
}

@showData
class User{
  constructor(){

  }
  @setDataFactory('name')
  getName(){}
  @setDataFactory('age')
  getAge(){}
}
const yyc = new User()
//output

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352