代码缺乏装饰?使用ts装饰器来装饰你的代码

简介: 该文章详细介绍了TypeScript中的装饰器特性,包括类装饰器、方法装饰器、属性装饰器和参数装饰器的定义与使用方法,并通过具体示例展示了装饰器在实际编码中的应用技巧。

👏序言

ts 中,有一个经常被我们熟用但是又很少去注意的一个知识点,装饰器。那在下文中,将讲解类的装饰器,一起类装饰器中的几种其他的装饰器。

下面开始本文的讲解~😜

😉一、类的装饰器

1、什么是装饰器

首先,我们先来讲, TypeScript 中,类的装饰器是什么。

装饰器实际上是一种对类的修饰工具。比如说:某一天可能有个女孩子想要出去逛街,那么她可能会画个美美的妆出门。因此,我们可以把装饰器视为是化妆的这个过程,也就是一个美化的过程。

现在就是,假设我们有一个类,然后呢,要对它额外进行一些修饰,这个就是装饰器要干的事情了。

2、装饰器的特点

首先我们需要先来了解装饰器的几个特点。具体如下:

  • 装饰器本身就是一个函数
  • 装饰器接收的参数是构造函数
  • 装饰器通过 @ 符号来进行使用。

依据以上这几个特点,下面我们来了解几种类的装饰器。

3、几种类的装饰器

(1)执行顺序

// 第一个装饰器
function testDecorator(constructor: any) {
   
  console.log('decorator');
}

// 第二个装饰器
function testDecorator1(constructor: any) {
   
  console.log('decorator1');
}

// 装饰器执行的时候,是从下到上,从右到左的顺序
@testDecorator
@testDecorator1
class Test {
   }

const test = new Test(); // decorator1 decorator

装饰器执行的时候,是从下到上,从右到左的顺序。

(2)参数判断

我们如何让类装饰器接收一个参数呢?来看一段代码:

// 外面再包一层函数
function testDecorator(flag: boolean) {
   
  // 工厂模式
  if (flag) {
   
    return function (constructor: any) {
   
      constructor.prototype.getName = () => {
   
        console.log('Monday');
      };
    };
  } else {
   
    return function (constructor: any) {
   };
  }
}

@testDecorator(true)
class Test {
   }

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

通过上面这段代码我们可以了解到,我们通过对类装饰器的外部再包上一层函数,这其实有点像柯里化的形式,之后通过外部的这个函数进行传参,也就是上面代码中的 flag 。最终类装饰器返回一个函数作为结果,顺利地进行传参。

(3)装饰器标准写法

上面的两个装饰器属于两个比较简单和不太规范的装饰器。下面我们来展现一种比较标准的写法:

function testDecorator() {
   
  return function <T extends new (...args: any[]) => any>(constructor: T) {
   
    return class extends constructor {
   
      name = 'Tuesday';
      getName() {
   
        return this.name;
      }
    };
  };
}

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

const test = new Test('Monday');
console.log(test.getName()); // Tuesday

在上面的代码中, (...args: any[]) => any 是一个函数,返回值是一个对象的类型。这个函数会接收很多参数,函数把这些参数合并到一起,变成一个数组,也就是 ...args 。那 <T extends new (...args: any[]) => any> 是什么意思呢?意思是, T 可以通过 new (...args: any[]) => any 这种类型的构造函数,给实例化出来。所以 T 现在可以理解为是一个类或者是 constructor 这样的一个构造函数。

最终,我们通过 testDecorator()() 这样的方式,让 test 实例可以访问到 getName() 方法,并打印出 Tuesday

🤐二、类的其他装饰器

1、方法装饰器

这里我想要强调的一个问题是,大家觉得,类装饰器的执行时刻是什么样的?类装饰器在类定义完成之后就可以立即对进行一个装饰。

那方法装饰器,是什么样的呢?

方法装饰器,跟类装饰器也是一样的。它会等类创建好了之后,立即地把方法去做一个修改。

很多小伙伴可能会误认为,我是不是得实例化的时候,才会对方法去做一个装饰呢?其实不是这样的,只要在定义完以后,就会帮助我们对类的方法去做一个装饰。先来看一段代码:

// 普通方法, target 对应的是类的 prototype
// 静态方法, target 对应的是类的 构造函数
function getNameDecorator(
  target: any,
  key: string,
  descriptor: PropertyDescriptor
) {
   
  // console.log(target);
  // descriptor的作用:对方法中的属性做一些编辑
  descriptor.writable = true;
  // 通过调用 .value 的方式,可以对原来的方法做一些变更
  descriptor.value = function () {
   
    return 'decorator';
  };
}

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

const test = new Test('Monday');
test.getName = () => {
   
  return '123';
};
console.log(test.getName()); // decorator

大家先看上面这段代码,可能有的小伙伴会觉得,最终打印的是 123 。但其实,因为我们对方法进行了装饰,所以最终打印的结果是 decorator

因此,一个装饰器对一个方法做完装饰之后,就可以多做很多事情了。包括原型target,key值 和 descriptor ,都可以对方法做很多修改。

2、访问器的装饰器

现在,我们来学习类里面中,访问器的装饰器。我们先来看一段代码:

function visitDecorator(
  target: any,
  key: string,
  descriptor: PropertyDescriptor
) {
   
  // console.log(123);
}

class Test {
   
  private _name: string;
  constructor(name: string) {
   
    this._name = name;
  }
  // 这里不能写@visitDecorator,同时写两个会引发报错
  get name() {
   
    return this._name;
  }
  @visitDecorator
  set name(name: string) {
   
    this._name = name;
  }
}

const test = new Test('Monday');
test.name = 'Tuesday';
console.log(test.name); // Tuesday

其中, @visitDecorator 是一个访问器装饰器。我们现在来解释下上面代码中的运行路径。

第一部分, test.name = 'Tuesday' 走的是 set 方法,把 Tuesday 这个值赋值给 name 。之后,等到我们运行 console.log 的时候,就是去调用 get 方法,所以最终打印出来的也就是 Tuesday 而不是 Monday

3、属性的装饰器

我们先来看第一种属性的装饰器。具体代码如下:

function nameDecorator(target: any, key: string): any {
   
  const descriptor: PropertyDescriptor = {
   
    writable: true,
  };
  return descriptor;
}

class Test {
   
  @nameDecorator
  name = 'Monday';
}

const test = new Test();
test.name = 'Tuesday';
console.log(test.name); // Tuesday

属性装饰器的写法,也是一个 decorator 的形式,即上述代码中的 @nameDecorator 。这个装饰器接收两个参数,分别是 原型target属性的名字key 。在这里我们可以返回一个 descriptor 来替换掉属性原始的 descriptor 。替换完成之后,最终打印 Tuesday


继续,我们来看第二种装饰器。具体代码如下:

// 该装饰器无法直接修改实例上的属性值(name),而只能修改原型上的属性值(name)
function nameDecorator(target: any, key: string): any {
   
  target[key] = 'Tuesday';
}

// name存储在类的实例上
class Test {
   
  @nameDecorator
  name = 'Monday';
}

const test = new Test();
test.name = 'Hello~';
console.log((test as any).name); // Hello~
console.log((test as any).__proto__.name); // Tuesday

这种类型的装饰器中,值得注意的点是, nameDecorator 只能用来修改原型上的属性值,而无法直接修改实例上的属性值。

4、参数装饰器

上面我们讲到了对类里面的方法、访问器和属性做修饰,现在,我们再来了解一种新的装饰器:对类里面的方法中的参数做修饰。先来看一段代码:

// 原型,方法名,参数所在的位置
function paramDecorator(target: any, key: string, paramIndex: number): any {
   
  console.log(target, key, paramIndex); // Test { getInfo: [Function] } , 'getInfo' , 1(参数所在位置是第2个位置)
}

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

const test = new Test();
test.getInfo('Monday', 18); // Monday 18

大家可以看到,通过对方法中的参数进行装饰,我们可以获取到装饰器的原型方法名参数所在的位置,这个就是参数装饰器。

😐三、装饰器实际使用的小例子

上面我们讲到了很多种装饰器相关的原理知识,现在我们用一个实际使用的例子来带大家更好的使用装饰器。先看一段代码:

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(); // userInfo.name 不存在
test.getAge(); // userInfo.age 不存在
test.getGender(); // userInfo.gender 不存在

在上面的代码中,我们做的是捕获异常的一个功能。通过封装 @catchError 装饰器,来对我们最终使用的三个方法,getNamegetAgegetGender ,对这三个方法进行异常捕获

以上算是对装饰器的一次小小的实践,后续深入学习可以再参考一些书籍去多练习。

😏四、结束语

在上面的文章中,我们讲解了装饰器中最基础的类装饰器,以及类装饰器中的4中其他类型的装饰器。最后,我们还用了一个小例子去简单地了解了,装饰器在实际应用中的一些操作。

到这里,关于装饰器的学习就接近尾声啦!不知道小伙伴们对装饰器又有了一些新的了解呢?

如果您觉得这篇文章有帮助到您的的话不妨点赞支持一下哟~~😉

我们下期再见!👋👋👋

🥳 往期推荐

🛵TypeScript,从0到入门带你进入类型的世界

相关文章
|
Python
一日一技:装饰器如何装饰异步函数
一日一技:装饰器如何装饰异步函数
163 0
|
6月前
|
数据安全/隐私保护 Python
解释装饰器(decorator)的功能和用法。
解释装饰器(decorator)的功能和用法。
51 1
|
Python
python装饰器中的4种类型(函数装饰函数、函数装饰类、类装饰函数、类装饰类)
python装饰器中的4种类型(函数装饰函数、函数装饰类、类装饰函数、类装饰类)
115 0
|
Python
Python 装饰器装饰类中的方法
Python 装饰器装饰类中的方法
87 0
|
程序员 Python
装饰器property用法及使用它重构代码时的应用实例
&emsp;&emsp;我们知道装饰器可以将所需要的函数作为对象返回给需要用此函数处理的对象。而property装饰器则是将一个函数或者方法成为一个属性以供使用。@property这种语法糖形式简化了装饰器的使用。 ### 二、引导 &emsp;&emsp;在面向对象类型的语言中访问修改类中的属性时通常会自己写set或者get函数,对输出结果进行修饰或者对输入进行合法化检查。然而python可以提供一种优美的更直观的方法就是使用装饰器@property。
101 0
ts重点学习115-类的装饰器
ts重点学习115-类的装饰器
62 0
ts重点学习115-类的装饰器
ts重点学习116-类的装饰器2
ts重点学习116-类的装饰器2
61 0
ts重点学习116-类的装饰器2
ts重点学习122-属性的装饰器
ts重点学习122-属性的装饰器
66 0
ts重点学习122-属性的装饰器
ts重点学习118-方法的装饰器
ts重点学习118-方法的装饰器
40 0
ts重点学习118-方法的装饰器
ts重点学习125-装饰器的小例子
ts重点学习125-装饰器的小例子
49 0
ts重点学习125-装饰器的小例子