快速了解 TS 装饰器

988 阅读2分钟

如果没有用过装饰器,很多人应该跟我一样对装饰器具体是个什么东西没什么概念,只知道它是一个样子像 @decorator 的东西,会对被装饰的东西产生影响,但是具体是怎么影响的不清楚。那么,就先用尽量简洁的语言来解释什么是装饰器:

装饰器本质上就是一个包装函数。它可以拿到被装饰的内容,对其进行自定义加工和处理。

一个抽象的解释之后,来看一个简单的例子:

// 装饰器实现
function decorateClass<T>(constructor: T) {
  console.log('decorateClass');
}

// 作为类装饰器
@decorateClass
class A {
  constructor() {}
}

// 输出结果: decorateClass

装饰器可以拿到类 A,对其进行加工。

装饰器的实现

刚刚已经看了一个简单的装饰器例子,但是装饰器的实现其实不止这种方式,简单总结就是:

  1. 不支持参数的装饰器

    function decorator(target) {
      // do something with 'target' ...
    }
    
  2. 支持参数的装饰器(装饰器工厂) 其实是一个工厂函数,返回一个装饰器方法。

    function decorator(value: string) {
      // this is the decorator factory, it sets up
      // the returned decorator function
      return function (target) {
        // this is the decorator
        // do something with 'target' and 'value'...
      };
    }
    

装饰器可以应用在哪些对象上?

知道装饰器的基本概念之后,自然也需要知道到底可以用来装饰什么东西,简单总结就是类相关的它都可以装饰:

  • 类(Class Decorators)
  • 类属性(Property Decorators)
  • 类访问器(Accessor Decorators)
  • 类方法(Method Decorators)
  • 类方法参数(Parameter Decorators)

类装饰器 - Class Decorators

作用:可以拿到类的构造函数,可以修改或替换类的定义

作用对象:类

参数:类的构造函数

返回值:如果带返回值,则需要是一个构造函数,该构造函数将替换原本类的实现

如下示例,返回一个新的类替换原本类的实现:

function decorateClass(constructor) {
  return class B extends constructor {
    name = 'B';
    age = 18;
  }
}

@decorateClass
class A {
  name = 'A';
  constructor() {
  }
}

const a = new A();
console.log(a.name, a.age)  // 输出 B 18
function decorateClass(name) {
  return function (constructor) {
    return class B extends constructor {
      name = name || 'B';
      age = 18;
    };
  };
}

@decorateClass('name')
class A {
  name = 'A';
  constructor() {}
}

const a = new A();
console.log(a.name, a.age); // 输出 name 18

类属性装饰器 - Property Decorators

作用:可以拿到属性名,对属性实现代理等操作

作用对象:静态成员、实例成员

参数

  1. target:对于静态成员则是构造函数,对于实例成员则是类的原型
  2. key:成员名

返回值:无

如下示例,对装饰的属性实现代理,检查设置的值是否合法:

function validProperty(validNameList: string[]) {
  return function (target: any, propertyKey: string) {
    let value: string = target[propertyKey];

    const get = function () {
      console.log(`${propertyKey} value: ${value}`);
      return value;
    };

    const set = function (val: string) {
      console.log('validNameList', validNameList);
      if (!validNameList.includes(val)) {
        console.warn(`invalid name: ${val}`);
        return;
      }
      value = val;
    };
    Object.defineProperty(target, propertyKey, { set, get });
  };
}

class People {
  @validProperty(['a', 'b'])
  name: string;

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

const people = new People('a');

console.log(people.name); // a
people.name = 'cccc'; // invalid name: cccc
console.log(people.name); // a

类访问器装饰器 - Accessor Decorators

作用:可以修改或替换访问器的定义

作用对象:静态访问器、实例访问器

参数

  1. target:对于静态访问器则是构造函数,对于实例访问器则是类的原型
  2. key:访问器名
  3. descriptor:属性描述符,即 Object.getOwnPropertyDescriptor(target,key)

返回值:如果带返回值,则需要是一个属性描述符,并作为访问器的属性描述符

如下示例,修改访问器的属性描述符:

function enumerable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = value;
  };
}

class People {
  @validProperty(['a', 'b'])
  firstName: string;

  lastName: string;

  @enumerable(false)
  get fullName() {
      return `${this.firstName} ${this.lastName}`;
  }

  constructor(firstName: string, lastName: string) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}

类方法装饰器 - Method Decorators

作用:可以修改或替换方法的定义

作用对象:静态方法、实例方法

参数

  1. target:对于静态方法则是构造函数,对于实例方法则是类的原型
  2. key:方法名
  3. descriptor:属性描述符,即 Object.getOwnPropertyDescriptor(target,key)

返回值:如果带返回值,则需要是一个属性描述符,并作为访问器的属性描述符

如下示例,增强方法,补充方法弃用提示:

const deprecated = (deprecationReason: string) => {
  return (target: any, memberName: string, propertyDescriptor: PropertyDescriptor) => {
    const wrapperFn = (...args: any[]) => {
        console.warn(`Method ${memberName} is deprecated with reason: ${deprecationReason}`);
        propertyDescriptor.value.apply(target, args)
    }
    return {
      value: wrapperFn,
    }
  }
}

class TestClass {
  static staticMember = true;

  instanceMember: string = "hello"

  @deprecated("Use another static method")
  static deprecatedMethodStatic() {
    console.log('inside deprecated static method - staticMember =', this.staticMember);
  }

  @deprecated("Use another instance method")
  deprecatedMethod () {
    console.log('inside deprecated instance method - instanceMember =', this.instanceMember);
  }
}

TestClass.deprecatedMethodStatic();

const instance = new TestClass();
instance.deprecatedMethod();

类方法参数装饰器 - Parameter Decorators

作用:待补充

作用对象:静态方法、实例方法

参数

  1. target:对于静态成员则是构造函数,对于实例成员则是类的原型
  2. key:参数名
  3. index:参数在函数参数列表中的索引

返回值:无

如下示例:

function print(target: Object, propertyKey: string, parameterIndex: number) {
  console.log(`Decorating param ${parameterIndex} from ${propertyKey}`);
}

class TestClass {
  testMethod(param0: any, @print param1: any) {}
}

小结

image.png

装饰器的应用

待补充

参考