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 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 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 '#'

访问器的装饰器

  • 访问器的装饰器和方法的装饰器大致相同
  • 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 '#'
  • 不能通过装饰器对属性值进行更改,因为更改的是原型上的而不是实例上的。
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

你可能感兴趣的:(Typescript学习笔记(21) ----- 装饰器(Decorator))