likes
comments
collection
share

前端想了解后端?那得先学会 TypeScript 装饰器!TypeScript 装饰器属于实验性特性,但在Nest.js

作者站长头像
站长
· 阅读数 24

前言

几年前学TypeScript时,装饰器属于实验性特性,因此没有关注,想等特性稳定后再看。现在开始了解Nest.js时,发现装饰器是必须掌握的内容,否则连入门都入不了(ಥ_ಥ) 。本篇文章将介绍TypeScript中装饰器的相关概念,适合准备学习Next.js的小伙伴。

1. 启用装饰器

  1. 新建文件夹ts

  2. 全局安装ts

npm install typescript -g
  1. 生成tsconfig.js配置文件
tsc --init
  1. 修改tsconfig.js配置文件,将原先被注释掉的"experimentalDecorators": true放开
{
    "compilerOptions":{
        "experimentalDecorators": true, 
    }
}

2. 装饰器概念

装饰器使用 @expression 的形式使用,expression 是一个函数,会在运行时被调用,被装饰的数据会作为参数传入到这个函数中。

  1. 新建index.ts
function decorator(target: any) {
    target.say = function () {
      console.log("hello!");
    };
  }
  
  @decorator
  class Animal {
    static say: Function;
    constructor() {}
  }
  
  Animal.say(); // hello!

代码中的 decorator 就是一个装饰器函数,接收一个 target 参数,decorator 装饰器修饰了 Animal 这个类,那么 Animal 类就被作为 target 参数传入到了 decorator 函数中。

  1. 让TypeScript编译器自动监听文件变化并进行编译,运行
tsc --watch

会依据index.ts文件,生成一份index.js文件。可以使用VSCode的插件Code Runner直接跑index.js文件。或者手动创建一个index.html,并将js文件引入

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="./index.js"></script>
</body>
</html>

使用浏览器打开html文件,浏览器控制台打印出hello!,说明Animal类中的say方法已被赋值。

从该示例可知,装饰器类似于高阶函数,都是通过其他函数,扩展了能力。但装饰器的语法更简单,只需要在想使用的装饰器前加上@符号,装饰器就会被应用到目标上。

3. 装饰器工厂

装饰器工厂通过 @expression(args) 形式使用,装饰器工厂中的 expression 会返回一个装饰器函数,args 是用户想自定义传入的参数。

相比于普通的装饰器,装饰器工厂可传入自定义的参数,修改index.ts

function decorator(name: string) {
  return function (target: any) {
    target.say = function () {
      console.log("My name is " + name);
    };
  };
}

@decorator("zhangsan")
class People1 {
  static say: Function;
  constructor() {}
}

People1.say(); // My name is zhangsan

@decorator("lisi")
class People2 {
  static say: Function;
  constructor() {}
}

People2.say(); // My name is lisi

函数decorator,它接受一个字符串 name 作为参数。decorator 函数本身返回一个内部函数,这个内部函数接收一个 target 参数,target 参数是指被装饰的类本身。

4. 多个装饰器的组合

可以对同一目标应用多个装饰器。修改index.ts

function decoratorName(name: string) {
  return function (target: any) {
    target.sayName = function () {
      console.log("My name is " + name);
    };
  };
}

function decoratorAge(age: number) {
  return function (target: any) {
    target.sayAge = function () {
      console.log("My age is " + age);
    };
  };
}

@decoratorName("zhangsan")
@decoratorAge(20)
class People1 {
  static sayName: Function;
  static sayAge: Function;
  constructor() {}
}

People1.sayName(); // My name is zhangsan
People1.sayAge(); // My age is 20

这里的代码打印出的结果为:

My name is zhangsan
My age is 20

5. 装饰器类型

装饰器共有五种类型,分别为:1. 类装饰器;2.属性装饰器;3.方法装饰器;4.参数装饰器;5.访问器装饰器;

// 类装饰器
@classDecorator
class Bird {
  // 属性装饰器
  @propertyDecorator
  name: string;

  // 方法装饰器
  @methodDecorator
  fly(
    // 参数装饰器
    @parameterDecorator
    meters: number
  ) {}

  // 访问器装饰器
  @accessorDecorator
  get egg() {}
}

1. 类装饰器

类装饰器在类声明之前被声明,用于类、构造函数,可以用来修改或添加类的属性和方法。在装饰器概念和装饰器工厂介绍中,都使用到了类装饰器。在这里定义一个装饰器函数decorator,并显式指定返回一个类装饰器。

function decorator(param?: string): ClassDecorator {
    return (target: any) => {
      target.say = function () {
        console.log("hello!");
      };
      target.run = function () {
        console.log("I am running.");
      };
    };
  }
  
  @decorator()
  class Animal {
    static say: Function;
    static run: Function;
    constructor() {}
  }
  
  Animal.say(); // hello!
  Animal.run(); // I am running.

2. 属性装饰器

属性装饰器声明在一个属性声明之前,它允许你修改类的属性或者获取关于类属性的信息。属性装饰器接收两个参数:类的原型(即类本身)和属性键名。

这里举个示例,通过属性装饰器,对属性进行拦截监听。

// 定义一个属性装饰器
function log(target: any, key: string) {
    let originalValue: any;

    Object.defineProperty(target, key, {
        get: function() {
            console.log(`Getting ${key}: ${originalValue}`);
            return originalValue;
        },
        set: function(value) {
            console.log(`Setting ${key} to ${value}`);
            originalValue = value;
        },
        enumerable: true,
        configurable: true
    });
}

// 使用属性装饰器
class Person {
    @log
    name: string;

    constructor(name: string) {
        this.name = name; // 这里会触发setter
    }
}

// 创建Person实例
const person = new Person("Alice");

// 访问name属性
console.log(person.name); // 这里会触发getter
person.name = "Bob"; // 这里会再次触发setter

之后想对某个属性进行监听,直接使用@log即可。

3. 方法装饰器

方法装饰器用于装饰类的方法,它们可以拦截、修改或增强方法的行为。它接收三个参数:类的原型(即类本身)、方法的名称以及方法的描述符。相比于属性装饰器,方法装饰器多了第三个参数,描述符。

这里举个示例,通过方法装饰器,将方法设置为不能改写。

function decorator(): MethodDecorator {
  return (target, propertyKey, descriptor) => {
    descriptor.writable = false;
  };
}

class A {
  @decorator()
  originMethod() {
    console.log("I'm Original!");
  }
}

const a = new A();

try {
  a.originMethod = () => {
    console.log("I'm Changed!"); //Cannot assign to read only property 'originMethod'
  };
} catch (e) {}

a.originMethod(); // I'm Original! 并没有被修改

4. 参数装饰器

参数装饰器用于装饰类方法的参数。主要用于元数据的注入、参数的验证或者参数的转换。它接收三个参数:类的原型、方法名以及参数在参数列表中的位置。相比于属性装饰器,参数装饰器多了第三个参数,参数在参数列表中的索引。

function decorator(params?: any): ParameterDecorator {
  return (target, propertyKey, index) => {
    console.log(target); //{}
    console.log(propertyKey); //say
    console.log(index); //1
  };
}

class Animal {
  say(name: string, @decorator() age?: number) {}
}

5. 访问器装饰器

访问器装饰器类似于方法装饰器,唯一的区别在于描述器中有的key不同

方法装饰器的描述器的key为:value,writable,enumerable,configurable

访问器装饰器的描述器的key为:get,set,enumerable,configurable

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

class Point {
  private _x: number;
  private _y: number;
  constructor(x: number, y: number) {
    this._x = x;
    this._y = y;
  }

  //不允许删除该属性
  @configurable(false)
  get x() {
    return this._x;
  }

  //不允许删除该属性
  @configurable(false)
  get y() {
    return this._y;
  }
}

相比于其他四种装饰器,访问器装饰器的使用场景较少。

结尾

參考文章:

mirone.me/zh-hans/a-c…

转载自:https://juejin.cn/post/7402960438861070399
评论
请登录