nest的基石-装饰器

156 阅读3分钟

上一篇 初识nest 中大致了解了 nest,同时可以看到不论是在 AppMoudle@Module 或者是 AppController@Controller, 还是在 AppService 中的 @Injectable, 不难发现,在 nest 中随处可见各种各样的装饰器

那么什么是装饰器呢?,又起到了什么作用呢

什么是装饰器

TypeScript 装饰器是一种特殊类型的声明,可以附加到类声明、方法、访问器、属性或参数上。装饰器提供了一种方式来修改类的行为或结构,而不需要改变类本身的代码。装饰器可以通过元编程的方式增强类的功能,使得代码更加灵活和可扩展。

简单来说,装饰器是一种非侵入式编程,不改变本身代码,是用来增强功能

类似于钢铁侠中的托尼斯塔克,可以佩戴不同的机甲作战,而本人不发生变化,不像绿巨人需要通过让自己变异来获取力量

一共有四种装饰器,分别是 类装饰器方法装饰器,属性装饰器,参数装饰器,它们都是由 @ 开头,紧跟一个函数名,区别是函数参数不同

本文所有代码已放在文章最后

类装饰器

定义类装饰器 classDecorator

@classDecorator
class Person {}

类的装饰器类型为 ts 的内置类型 ClassDecorator

declare type ClassDecorator =
    <TFunction extends Function>(target: TFunction) => TFunction |  > void;

我们来定义 classDecorator, 参数只有一个,当前实例

我们可以在他的原型上添加属性,所有的实例共用,比如添加 name 属性

const classDecorator:ClassDecorator = (target)=>{
  target.prototype.name = "tsk";
}

那么在所有的实例上都可以获取到 name 属性

const tsk = new Person()
tsk.name   // tsk

对于一些不需要动态属性的话,可以使用这种方法

很明显每个人的名字是不尽相同的,所以我们使用高阶函数创建

工厂函数

传入参数 tsk

@classDecorator('tsk')
class Person {}

使用高阶函数,接受参数并返回一个函数

const classDecorator = (name:string):ClassDecorator=>{
  return (target) => {
    target.prototype.name = name;
  }
}  

如果想要改变姓名,可以直接在装饰器中输入不同的参数即可

const tsk = new Person()
console.log(tsk.name) // tsk

code.png

方法装饰器

顾名思义对类中的方法做装饰

class Person {
  @methodDecorator
  showName() {
    console.log("我是Tsk")
  }
}

方法装饰器类型为MethodDecorator, 使用了泛型T,可以传入Function 获取更好的提示

declare type MethodDecorator = <T>(
    target: Object, 
    propertyKey: string | symbol, 
    descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
    
interface TypedPropertyDescriptor<T> {
    enumerable?: boolean;
    configurable?: boolean;
    writable?: boolean;
    value?: T;
    get?: () => T;
    set?: (value: T) => void;
}

我们定义名为 methodDecorator 的方法, 方法装饰器返回三个参数,分别是 原型对象, 方法名 , 属性描述符

const methodDecorator: MethodDecorator = (target, key, descriptor) => {
  console.log(target, key, descriptor); 
};

打印出来看看 image.png

同样的,由于第一个参数是 原型对象,我们可以直接在上面加属性

比如添加 sex 属性:

const methodDecorator: MethodDecorator = (target, key, descriptor) => {
  target.sex = "男"
};

在实例对象上可以直接使用

const tsk = new Person()
console.log(tsk.sex) // 男

第三个参数是属性描述符,描述符的 value 属性被装饰的函数体 我们可以直接调用

const methodDecorator: MethodDecorator = (target, key, descriptor) => {
  descriptor.value()  // 我是Tsk 
};

那么我们也可以修改函数体

const methodDecorator: MethodDecorator = (target, key, descriptor) => {
// 更改函数体
  descriptor.value = function(){
    console.log("我换名字了")
  }
};
// ......
const tsk = new Person()
tsk.showName() // 我换名字了

根据这个特性,我们可以做切面编程

const methodDecorator: MethodDecorator = <Function>(target, key, descriptor) => {
  let oldValue = descriptor.value;
  descriptor.value = function(){
    oldValue()
    console.log(`调用了方法${key}`)
  }
};
// ....
const tsk = new Person()
tsk.showName() // 我是Tsk,调用了方法showName

code.png

属性装饰器

依旧是我们熟悉的用法

class Person {
  @ageDecorator
  age: number;

  constructor(age: number) {
    this.age = age;
  }
}

装饰器类型为 ts 的内置类型 PropertyDecorator

declare type PropertyDecorator =
   (target: Object, propertyKey: string | symbol) => void;

返回两个参数,分别是 原型对象属性名

const ageDecorator:PropertyDecorator = (target, key) =>{
 console.log(target,key) // Person, 'age'
 target.sex = "男"
}

那么就在实例上可以使用

const person = new Person(20);
console.log(person.sex); // 男

在 nest 中的一个经典应用,使用 class-validator 校验类型

import {IsNumber} from "class-validator"
export class Person {
  @IsNumber({ message:"taskId 必须是一个数字" })
  taskId:number
}

如果类型不对,就会爆出taskId 必须是一个数字错误

我们来实现一下,首先IsNumber 是一个高阶函数,接受 一个对象,使用属性访问器 对 key 进行 定义

code.png

image.png

参数装饰器

class Person {
  show(@parameterDecorator name:string){}
}

参数装饰器类型为ParameterDecorator

declare type ParameterDecorator = (
    target: Object, 
    propertyKey: string | symbol,
    parameterIndex: number) => void;

实现 装饰器,返回三个参数,原型对象方法名参数位置下标

const parameterDecorator:ParameterDecorator = (target,key,index)=>{
  console.log(target,key,index) // Peson,'show' ,0
}

不再过多介绍

总结

  1. 装饰器可以在不改变原有代码的情况下,对程序进行一定程度的增强
  2. 装饰器可以分为4类,分别是类装饰器,方法装饰器,属性装饰器,参数装饰器
  3. 类装饰器只有1个参数,是当前原型对象
  4. 方法装饰器有4个参数 原型对象,方法名,属性描述符
  5. 属性装饰器有2个参数,原型对象与属性名
  6. 参数装饰器有3个参数,原型对象,方法名,参数位置下标

在 NestJS 中使用装饰器用于增强和组织应用程序的各个方面,包括定义模块、控制器和服务,处理路由和请求参数,注入依赖,添加中间件和异常处理等,从而实现模块化、可维护和高度可扩展的应用程序架构。

后续会不断的介绍不同的装饰器在 nest 中的应用

本文所使用的所有代码

const classDecorator = (name: string): ClassDecorator => {
    return (target) => {
        target.prototype.name = name;
    }
}

const methodDecorator: MethodDecorator = <Function>(target, key, descriptor) => {
    let oldValue = descriptor.value;
    descriptor.value = function () {
        oldValue()
        console.log(`调用了方法${key}`)
    }
};


const IsNumber = ({ message }: { message: string }): PropertyDecorator => {
    return (target, key) => {
        // 获取当前值
        let value = target[key];

        const getter = function () {
            return value;
        };

        const setter = function (newVal: number) {
            if (typeof newVal !== 'number' || isNaN(newVal)) {
                throw new Error(`${message}`);
            }
            value = newVal;
        };

        Object.defineProperty(target, key, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true,
        });
    }
}

@classDecorator('tsk')
class Person {

    @methodDecorator
    showName() {
        console.log("我是Tsk")
    }

    @IsNumber({ message: "必须是一个数字" })
    age;

    constructor(age: any) {
        this.age = age;
    }
}

const tsk = new Person("123")
tsk.showName()   // tsk