typeScript--再学装饰器

2023-04-03  本文已影响0人  习惯水文的前端苏

好文推荐

localStorage的别样用法
借助npm包统一包管理器

前言

约是两年前,就对着ts文档学习了装饰器的使用,我是相信温故而知新的,故今天重新在学一遍,相信会有不一样的收获

启用

在项目中,需要在tsconfig.json中显示的启用

{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

装饰器分类

1-类装饰器
2-属性装饰器
3-方法装饰器
4-参数装饰器
5-访问器装饰器

执行顺序

使用

1-其ts定义如下

可以看到,类的本质是Function类型,这意味着,我们可以通过prototype来进行扩展,同时从返回值可以猜测,如果返回值类型不是void,则极大可能性会将返回的值作为装饰器本身

declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;

2-返回void

function sex<T extends {new(...args:any[]):{}}>(constructor:T){
  constructor.prototype.logSex = function(){
    console.log('男')
  }
}

@sex
class Person{
}

(new Person() as any).logSex()

3-返回Function

上一个返回void的示例是无法传递参数的,可以通过返回函数类型来解决

function sex(msg:string){
  return function<T extends {new(...args:any[]):{}}>(constructor:T){
    constructor.prototype.logSex = function(){
      console.log(msg)
    }
  }
}

@sex('用户的')
class Person{
}
(new Person() as any).logSex()

4-重载

由于class本质上也是Function类型,所以这可以看作是3的另一种表达形式,且更灵活些

function sex<T extends {new(...args:any[]):{}}>(Tar:T){
  return class extends Tar{
    constructor(...args:any[]){
      super()
    }
  }
}

@sex
class Person{
}

1-定义

从定义可知,其也是一个函数,且被执行时会接收两个参数

1-对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
2-成员的名字

declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;

2-使用

我们可以对某个属性进行监控,并当某个操作行为发生时执行一些关联动作

function reactive(Tar:any,p:string){
  let value = Tar[p]

  const get = function(){
    return value
  }

  const set = function(newValue:any){
    Tar.changeName(newValue)
    value = newValue
  }

  if(Reflect.deleteProperty(Tar,p)){
    Object.defineProperty(Tar,p,{
      set,
      get,
      enumerable:true,
      configurable:true
    })
  }

}

class Person{
  @reactive
  name:string=''
  changeName(newName:string){
    console.log('改名字了,新名字是:'+newName)
  }
}
const p = new Person() 
p.name
p.name = '23'
console.log(p.name)

1-定义

declare type MethodDecorator = 
<T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) 
=> TypedPropertyDescriptor<T> | void;

相比较属性选择器,其多了参数三,其对应的ts定义如下

value属性表示的是函数体,这意味着可以先将原函数体缓存后执行重写等操作

interface TypedPropertyDescriptor<T> {
    enumerable?: boolean;
    configurable?: boolean;
    writable?: boolean;
    value?: T;
    get?: () => T;
    set?: (value: T) => void;
}

2-示例

拿我之前写npm包时遇到的一个需求举例,在初始化的时候用到了异步数据,在异步执行结束前如果用户执行了do方法,则需要对其进行缓存,等异步结束后再取回依次调用,伪代码如下

import { config } from '.'

function delay(_,_, descriptor){
  const value = descriptor.value
  descriptor.value = function(...args:any[]){
    if(config.asyning){
        config.que.push(descriptor)
    }else{
      value.apply(this,args)
      config.que = []
    }
  }
  return descriptor
}

class NAS{
  @delay
  do(){
  }
}

1-定义

declare type ParameterDecorator = 
(target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

2-示例

在参数装饰器执行时收集必选信息,当运行方法装饰器时重写descriptor.value并加入判断逻辑

const requiredMap:any = {

}
function required(_: any, propertyKey: string, parameterIndex: number){
  requiredMap[propertyKey]=parameterIndex
}

function validate(_:any,__:any, descriptor:any){
  const i = requiredMap[descriptor.value.name]
  if(i !== undefined){
    descriptor.value = (...args:any[])=>{
      if(args[i] === undefined){
        throw new Error('参数缺失')
      }
    }
  }
  return descriptor
}

class Person{
  @validate
  changeName(@required newName:string){
    console.log('改名字了,新名字是:'+newName)
  }
}
const p = new Person() 
p.changeName()

看文档吧,感觉没啥可说的,主要没实际用过,后续有应用场景了补充

上一篇下一篇

猜你喜欢

热点阅读