【查漏补缺系列-ts篇】装饰器

92 阅读4分钟

前言: 本类文章初衷只用作记录个人的学习博客,哪里有漏补哪里,不做任何其他商业用途。欢迎讨论,不喜勿喷。后面要是有遗漏的相关知识点,也会相应的补上。如果本篇文章能帮到你,我也会很愉快,共勉😁

目录

  1. 编译环境
  2. 装饰器
  3. 踩坑

编译环境

全局安装Typescript:

  • npm install typescript -g

全局安装ts的编译工具

  • npm install ts-node -g

ts-node xxx.ts运行ts文件

装饰器

装饰器是一种特殊的声明,它能够被附加到类声明、方法、访问符、属性和参数上。装饰器使用@fun这种形式,fun为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

若要使用装饰器,需先在tsconfig.json中启用experimentalDecorators选项:

{
  "compilerOptions": {
      "target": "ES5",
      "experimentalDecorators": true
  }
}

装饰器的写法

普通装饰器

function Format(data) {
  console.log(data) // [Function: Greeter]
}

@Format
class Greeter {}

const greeter = new Greeter()

装饰器工厂

装饰器工厂和普通装饰器的写法区别:

  • 普通装饰器无法传参,装饰器工厂可传参
  • 普通装饰器的默认参数是一个被装饰的声明信息,装饰器工厂的参数是自己传入的参数
  • 装饰器工厂需要返回一个函数,该函数会接受一个被装饰的声明信息
function Format(value: string) {
  return (target) => {
    console.log(value) // valid
    console.log(target) // [Function: Greeter]
  }
}
@Format('valid')
class Greeter {}

const greeter = new Greeter()

装饰器组合

当多个装饰器应用于一个声明上时,会进行如下步骤的操作:

  • 由上至下一次对装饰器表达式求值
  • 求值的结果会被当做函数,由下至上依次调用
function Format(value: string) {
  return (target) => {
    console.log('Format')
  }
}

function ReflectTarget(target) {
  console.log('ReflectTarget')
}

@ReflectTarget
@Format('valid')
class Greeter {}

const greeter = new Greeter()

/**
 * 执行顺序:
 * Format
 * ReflectTarget
 */

类装饰器

在类装饰器中,普通装饰器的参数,以及装饰器工厂返回函数的参数都是类的constructor

function ReflectTarget(target) {
  console.log('ReflectTarget', target)
  target.prototype.status = 'success'
  target.prototype.sayHi = () => 'Hello World';
}

@ReflectTarget
class Greeter {
  [x: string]: any;
  constructor() {}
}

const greeter = new Greeter()

console.log(greeter.status) // success
console.log(greeter.sayHi()) // Hello World

方法装饰器

方法装饰器声明在一个方法的声明之前。可以用来监视,修改或者替换方法定义。

方法装饰器会在运行时当做函数被调用,传入以下3个参数:

  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  • 成员的key
  • 成员的属性描述符

下面是一个修改最后返回结果的一个方法装饰器:


function RefVoid() {
  console.log('ReflectTarget')
  return (target: any, key: any, descriptor: any) => {

    descriptor.value = (value: number) => {
      return value * 2
    }
  }
}

class Greeter {
  constructor() {}
  
  @RefVoid()
  log(value: number) {
    return value
  }
}

const greeter = new Greeter()

console.log(greeter.log(10))  // 20

访问器装饰器

声明在一个访问器的声明前。用以监视,修改或替换一个访问器的定义。参数与方法装饰器一致。

function RefConfigurable() {
  console.log('RefConfigurable')
  return (target: any, key: any, descriptor: any) => {
    console.log(target, key, descriptor)
    descriptor.configurable = false
  }
}

class Greeter {
  x: number = 10
  constructor() {
  }

  @RefConfigurable()
  get getX() {
    return this.x
  }
}

const greeter = new Greeter()


console.log(greeter.getX) // 10

属性装饰器

声明在一个属性声明之前。会在运行时当做函数被调用,传入下列2个参数:

  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  • 成员的key
function RefAttribute()() {
  console.log('RefAttribute')
  return (target: any, key: any) => {
    console.log(target, key)
  }
}

class Greeter {
  @RefAttribute()
  count: number = 10
  constructor() {
  }

}

const greeter = new Greeter()


console.log(greeter.count) // 10

参数装饰器

声明在一个方法参数的声明之前。会在运行时当做函数被调用,参数装饰器的返回值会被忽略,且只能用来监视一个方法的参数是否被传入 传入下列3个参数:

  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  • 方法名字
  • 参数在方法中的索引
function RefRequired() {
  console.log('RefRequired')
  return (target: any, key: any, index: number) => {
    console.log(index) // 1
  }
}

class Greeter {
  constructor() {
  }

  log(msg: string, @RefRequired() count: number) {
    return msg
  }
}

const greeter = new Greeter()


console.log(greeter.log('Hello', 12)) // Hello

踩坑

  • nextJs使用装饰器会报错,这是next在提示缺少装饰器的babel,需要在.babelrc中配置兼容写法。

报错如下:

// x Unexpected token `@`. Expected identifier, string literal, numeric literal or [ for the computed key

解决方法:

{
  "presets": ["next/babel"],
  "plugins": [
    "@emotion",
    // 以下两行兼容装饰器写法
    "babel-plugin-transform-typescript-metadata",
    ["@babel/plugin-proposal-decorators", { "legacy": true }]
  ]
}

参考链接