在上一篇文章中,我们介绍了装饰器的基本概念,并通过一个简单的例子来展示了装饰器的使用方法。然后,Typescript同时支持装饰器的新语法和旧语法。本文来介绍Typescript的旧语法。 在本文中,所使用的语法均为旧语法。
1.装饰器配置
如果通过旧语法使用装饰器,需要在tsconfig.json中配置compilerOptions.experimentalDecorators为true。
{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}
2.装饰器的类型
- 属性装饰器
 - 方法装饰器
 - 参数装饰器
 - 类装饰器
 
3. 装饰器语法
3.1 属性装饰器
function propertyDecorator(target: Object, propertyKey: string | symbol) {
  // 目标对象
  console.log(target);
  // 目标对象的属性名
  console.log(propertyKey);
}
3.2 方法装饰器
function methodDecorator(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor) {
  // 目标对象
  console.log(target);
  // 目标对象的属性名
  console.log(propertyKey);
  // 目标对象的属性描述符
  console.log(descriptor);
}
3.3 参数装饰器
function parameterDecorator(target: Object, propertyKey: string | symbol | undefined, parameterIndex: number) {
  // 目标对象
  console.log(target);
  // 目标对象的属性名
  console.log(propertyKey);
  // 目标对象的参数索引
  console.log(parameterIndex);
}
3.4 类装饰器
function classDecorator(target: Function) {
  // 目标对象
  console.log(target);
}
4.装饰器的使用
/**
 * 定义装饰器命名空间
 */
namespace LegacyDecorators {
  /**
   * 最大值的key
   * @private
   */
  const maxKey = Symbol("maxKey");
  /**
   * 最小值的key
   * @private
   */
  const minKey = Symbol("minKey");
  /**
   * 属性装饰器:使属性变成可观察的
   */
  export function observable(): PropertyDecorator {
    function observe(type: 'setter' | 'getter', value: any) {
      console.log(`【observable】 ${value} is ${type}`)
    }
    return function (target: Object, propertyKey: string | symbol) {
      let temp: any;
      Object.defineProperty(target, propertyKey, {
        get() {
          observe('getter', temp);
          return temp;
        },
        set(value) {
          temp = value;
          observe('setter', temp);
        }
      });
    }
  }
  /**
   * 方法装饰器:打印日志
   */
  export function logger(): MethodDecorator {
    return function (target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<any>) {
      const originalMethod = descriptor.value;
      descriptor.value = function (...args: any[]) {
        console.log(`%c【logger】${target.constructor.name}.${propertyKey.toString()} has been invoked.`, "color: #ff00ff");
        originalMethod.apply(this, args);
      }
    }
  }
  /**
   * 方法装饰器:校验参数
   */
  export function validate(): MethodDecorator {
    return function (target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<any>) {
      const {min, index: minIndex} = Reflect.get(target, minKey);
      const {max, index: maxIndex} = Reflect.get(target, maxKey);
      const originalMethod = descriptor.value;
      descriptor.value = function (...args: any[]) {
        const minValue = args[minIndex];
        const maxValue = args[maxIndex];
        if (minValue !== undefined && minValue < min) {
          throw new Error(`The min value must be greater than ${min}.`);
        }
        if (maxValue !== undefined && maxValue > max) {
          throw new Error(`The max value must be less than ${max}.`);
        }
        return originalMethod.apply(this, args);
      }
    }
  }
  /**
   * 参数装饰器:最大值,需要配合@validate使用
   * @param max
   */
  export function max(max: number): ParameterDecorator {
    return function (target: Object, propertyKey: string | symbol | undefined, index: number) {
      Reflect.set(target, maxKey, {max, index});
    }
  }
  /**
   * 参数装饰器:最小值,需要配合@validate使用
   * @param min
   */
  export function min(min: number): ParameterDecorator {
    return function (target: Object, propertyKey: string | symbol | undefined, index: number) {
      Reflect.set(target, minKey, {min, index});
    }
  }
  /**
   * 类装饰器:密封类,使其不可拓展
   */
  export function sealed(): ClassDecorator {
    return function <T extends Function>(target: T): T | void {
      Object.seal(target);
    }
  }
}
// 以下通过实例演示装饰器的使用
@LegacyDecorators.sealed() // 这里表示类被密封
class Cat {
  @LegacyDecorators.observable() // 这里使name属性变成了可观察的
  name?: string;
  @LegacyDecorators.logger() // 这里进行日志打印
  @LegacyDecorators.validate() // 这里进行参数校验
  sayHello(/*最大值为10,最小值为1*/@LegacyDecorators.max(10) @LegacyDecorators.min(1) age: number) {
    console.log(`Hello, my name is ${this.name}, I'm ${age} years old.`);
  }
}
// 实例化对象
const cat = new Cat();
// 调用name属性的setter
cat.name = "Tom";
// 调用sayHello方法
cat.sayHello(3);
// 打印Cat是否被密封
console.log(`Cat is sealed: ${Object.isSealed(Cat)}`);
5.装饰器的执行顺序
装饰器的执行顺序是先执行属性装饰器,再执行方法装饰器,再执行参数装饰器,最后执行类装饰器。同一类型装饰器,按照由下到上的顺序执行。
6. 总结
虽然Typescript同时支持装饰器的新旧两种语法,但不能混用,要么使用新语法,要么使用旧语法。而且两种装饰器在功能和类型上也不尽相同。需要根据具体场景来决定使用哪种装饰器。
欢迎在评论区留下您的见解~