关于TS装饰器

120 阅读3分钟

背景

最近在看一个项目用到了装饰器,但是之前使用TS从来没有接触,因此这里学习并记录一下。

介绍

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

在语法上,装饰器有如下几个特征。

(1)第一个字符(或者说前缀)是@,后面是一个表达式。

(2)@后面的表达式,必须是一个函数(或者执行后可以得到一个函数)。

(3)这个函数接受所修饰对象的一些相关值作为参数。

(4)这个函数要么不返回值,要么返回一个新对象取代所修饰的目标对象。

装饰器的context参数

context对象的属性,根据所装饰对象的不同而不同,其中只有两个属性(kindname)是必有的,其他都是可选的。

(1)kind:字符串,表示所装饰对象的类型,可能取以下的值。

  • 'class'
  • 'method'
  • 'getter'
  • 'setter'
  • 'field'
  • 'accessor'

这表示一共有六种类型的装饰器。

(2)name:字符串或者 Symbol 值,所装饰对象的名字,比如类名、属性名等。

(3)addInitializer():函数,用来添加类的初始化逻辑。以前,这些逻辑通常放在构造函数里面,对方法进行初始化,现在改成以函数形式传入addInitializer()方法。注意,addInitializer()没有返回值。

(4)private:布尔值,表示所装饰的对象是否为类的私有成员。

(5)static:布尔值,表示所装饰的对象是否为类的静态成员。

(6)access:一个对象,包含了某个值的 get 和 set 方法。

类装饰器

TypeScript中的类装饰器应用场景主要包括提供元编程能力、‌实现高级概念如依赖注入、‌监视和修改类的方法定义、‌以及监视、‌修改或替换类的属性定义。‌

当装饰器作用于类上时,有两个参数
参数1: 这个类本身
参数2: 一个context 如

{
  kind: 'class',
  name: 'Person',
  metadata: undefined,
  addInitializer: [Function (anonymous)]
}

类装饰器一般用来对类进行操作,可以不返回任何值

一些实际Demo

实现添加方法


    当不使用装饰器时候继承需要这么写
class Person {
  private name: string;
  private age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  introduce(){
    console.log(`My name is ${this.name} and I am ${this.age} years old.`);
  }
}

const per = new Person('张三',18)
per.introduce()

// 控制台打印如下
// My name is 张三 and I am 18 years old.

当使用装饰器

function addIntroduction(target: Person) {
  target.prototype.introduce = function() {
    console.log(`My name is ${this.name} and I am ${this.age} years old.`);
  };
}

@addIntroduction
class Person {
  private name: string;
  private age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}
const per = new Person('张三',18)
//@ts-ignore
console.log(per.introduce());

// 控制台打印如下
// My name is 张三 and I am 18 years old.

那这么写的好处是什么呢, 比如我多个类都有相同的方法可以抽离出去使用一个装饰器进行添加

实现添加属性


function addIntroduction(target: Person) {
 //@ts-ignore
  target.prototype.introduce = function() {
    console.log(`My name is ${this.name} and I am ${this.age} years old, I am from ${this.address}`);
  };
}


function addAddress(constructor: Function) {
  constructor.prototype.address = 'NanJing';
}

 //@ts-ignore
@addIntroduction
@addAddress
class Person {
  private name: string;
  private age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}
const per = new Person('张三',18)
//@ts-ignore
console.log(per.introduce());

// 控制台打印如下
// My name is 张三 and I am 18 years old, I am from NanJing

方法装饰器

**方法装饰器是一种特殊的声明,‌它可以附加到类的方法上,‌用于改变该方法的行为。‌装饰器通过修改方法的行为,‌可以扩展类的方法功能。‌。‌

方法装饰器的使用通常涉及到一个函数,‌该函数接受三个参数:‌目标对象、‌属性名和属性的描述符。‌通过修改这个描述符的值,‌可以改变方法的行为。‌例如,‌可以通过装饰器在方法执行前后添加日志记录,‌或者在方法执行时进行一些额外的检查或操作。‌

当装饰器作用于方法上时,有两个参数
参数1: 这个方法本身
参数2: 一个context 如

{
  kind: 'method',
  name: 'dance',
  static: false,
  private: false,
  access: { has: [Function: has], get: [Function: get] },
  metadata: undefined,
  addInitializer: [Function (anonymous)]
}

一些DEMO

对方法参数进行校验

// 帮我写一个必须是字符串的装饰器
function mustBeString(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = target
  let backFn = function (...args: any[]) {
    if (typeof args[0] !== 'string') {
      throw new Error(`must be a string`);
    }
    //@ts-ignore
    return originalMethod.apply(this, args);
  };
  return backFn;
}

class Person {
  private name: string;
  private age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

 //@ts-ignore
  @mustBeString
  dance(name:string){
    console.log(`dance ${name}`);
  }
}
const per = new Person('张三',18)
//@ts-ignore
per.dance({})

//打印结果
// 报错 `must be a string`

访问器装饰器

装饰器作用于访问器方法时,接收参数与返回值作用和作用于方法时相同。其中,作用于实例访问器方法时,target 接收的是类的 [[prototype]],作用于静态访问器方法时接收的是类本身。

未完成待补充.....