TypeScript高级语法重温系列(五)

302 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情

1. 类的装饰器

  • 装饰器本身是一个函数

  • 类装饰器接受的参数是构造函数

  • 装饰器通过 @ 符号来使用

  • 类的装饰器的执行时机:只在类定义时执行一次

function testDecorator(flag: boolean) {
  if (flag) {
    return function(constructor: any) {
      constructor.prototype.getName = () => {
        console.log('dell');
      };
    };
  } else {
    return function(constructor: any) {};
  }
}

@testDecorator(true)  // 这里也可以定义多个装饰器,但执行顺序是:先收集的装饰器后执行(即从下到上或者从右到左执行装饰器)
class Test {}

const test = new Test();
(test as any).getName();

以上类装饰器的写法容易理解,但没有代码提示的功能。以下是更复杂但标准合理的类装饰器的写法,且有代码提示的功能(factory工厂模式)

function testDecorator() {
  // new (...args: any[]) => any这是一个构造函数,可以接受包含很多元素的数组作为参数,每个元素都是any类型,且构造函数的返回值也是any类型
  return function<T extends new (...args: any[]) => any>(constructor: T) {
    return class extends constructor {
      name = 'lee';
      getName() {
        return this.name;
      }
    };
  };
}

const Test = testDecorator()(
  class {
    name: string;
    constructor(name: string) {
      this.name = name;
    }
  }
);

const test = new Test('dell');
console.log(test.getName());// 此时就有代码提示功能了,且name为lee

2.方法装饰器

方法装饰器的参数有三个

// 普通方法,target 对应的是类的 prototype
// 静态方法,target 对应的是类的构造函数

function getNameDecorator(target: any, key: string, descriptor: PropertyDescriptor) { //key是用了装饰器的方法名
  // console.log(target, key);
  // descriptor.writable = true;  // 该属性用来设置方法是否能被修改
  descriptor.value = function() {  // 这里是用来修改方法的返回值
    return 'decorator';
  };
}

class Test {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  @getNameDecorator
  getName() {
    return this.name;
  }
}

const test = new Test('dell');
console.log(test.getName());

3.访问器的装饰器

访问器装饰器的参数和方法装饰器的参数(有三个)都是一样的。

访问器的使用方式:只能在get或者set方法中的一个使用,不能两个都同时用

function visitDecorator(target: any, key: string, descriptor: PropertyDescriptor) { // 第一个参数是原型
  // descriptor.writable = false;
}

class Test {
  private _name: string;
  constructor(name: string) {
    this._name = name;
  }
  get name() {
    return this._name;
  }
  @visitDecorator
  set name(name: string) {
    this._name = name;
  }
}

const test = new Test('dell');
test.name = 'dell lee';
console.log(test.name);

4.属性的装饰器

属性装饰器的参数只有两个。第一个是原型,第二个是key(即用了装饰器的属性名)

// 当装饰器返回一个descriptor对象时,就相当方法和访问器装饰器的第三个参数
// function nameDecorator(target: any, key: string): any {
//   const descriptor: PropertyDescriptor = {
//     writable: false
//   };
//   return descriptor;
// }

// test.name = 'dell lee';

// 修改的并不是实例上的 name, 而是原型上的 name
function nameDecorator(target: any, key: string): any {
  target[key] = 'lee';
}

// name 放在实例上
class Test {
  @nameDecorator
  name = 'Dell';
}

const test = new Test();
console.log((test as any).__proto__.name);

5.参数装饰器

参数装饰器的参数有三个,分别是原型,方法名,参数所在的位置

// 原型,方法名,参数所在的位置
function paramDecorator(target: any, method: string, paramIndex: number) {
  console.log(target, method, paramIndex);
}

class Test {
  getInfo(name: string, @paramDecorator age: number) {
    console.log(name, age);
  }
}

const test = new Test();
test.getInfo('Dell', 30);

6.装饰器实际使用的小例子

异常捕获中使用方法装饰器可以提高代码的复用性,如下:

const userInfo: any = undefined;

function catchError(msg: string) { // 这里是工厂模式,主要是为了引入参数来指出具体是哪个不存在
  return function(target: any, key: string, descriptor: PropertyDescriptor) {
    const fn = descriptor.value;
    descriptor.value = function() {
      try {
        fn();
      } catch (e) {
        console.log(msg);
      }
    };
  };
}

class Test {
  @catchError('userInfo.name 不存在')
  getName() {
    return userInfo.name;
  }
  @catchError('userInfo.age 不存在')
  getAge() {
    return userInfo.age;
  }
  @catchError('userInfo.gender 不存在')
  getGender() {
    return userInfo.gender;
  }
}

const test = new Test();
test.getName();
test.getAge();