装饰器类型

类装饰器

类装饰器在类声明之前声明,应用于类的构造函数,可以用来观察、修改或替换类定义

类装饰器的 target 参数是类的构造函数,可以修改类的原型或添加静态属性:

function ClassDecorator(target: Function) {
  // target 是类的构造函数
  console.log('ClassDecorator called on', target);
  // 可以修改类的原型
  target.prototype.customProperty = 'custom value';
}

@ClassDecorator
class MyClass {
  constructor() {}
}

const instance = new MyClass();
console.log(instance.customProperty); // 输出: custom value

输出结果

ClassDecorator called on [class MyClass]
custom value
instance.customProperty: custom value

方法装饰器

方法装饰器应用于方法的属性描述符,可以用来修改方法的行为或添加元数据

方法装饰器接收三个参数:类的原型对象、方法名和属性描述符:

function MethodDecorator(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor,
) {
  console.log('方法装饰器被调用');
  console.log('target:', target); // 原型对象
  console.log('propertyKey:', propertyKey); // 方法名
  console.log('descriptor:', descriptor); // 属性描述符

  // 可以修改方法行为
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log('方法执行前');
    const result = originalMethod.apply(this, args);
    console.log('方法执行后');
    return result;
  };
}

class Example {
  @MethodDecorator
  public greet(message: string): void {
    console.log(`Hello, ${message}!`);
  }
}

输出结果

方法装饰器被调用
target: {}
propertyKey: greet
descriptor: {
  value: [Function: greet],
  writable: true,
  enumerable: false,
  configurable: true
}

属性装饰器

属性装饰器应用于类的属性,可以用来定义属性的元数据或修改属性的行为

属性装饰器接收两个参数:类的原型对象和属性名:

function PropertyDecorator(target: any, propertyKey: string) {
  console.log('属性装饰器被调用');
  console.log('target:', target); // 原型对象
  console.log('propertyKey:', propertyKey); // 属性名
}

class Example {
  @PropertyDecorator
  public name: string = 'test';
}

输出结果

属性装饰器被调用
target: {}
propertyKey: name

参数装饰器

参数装饰器应用于方法或构造函数的参数,可以用来定义参数的元数据

参数装饰器接收三个参数:类的原型对象、方法名和参数索引:

function ParameterDecorator(
  target: any,
  propertyKey: string | undefined,
  parameterIndex: number,
) {
  console.log('参数装饰器被调用');
  console.log('target:', target); // 原型对象或构造函数
  console.log('propertyKey:', propertyKey); // 方法名,构造函数参数为undefined
  console.log('parameterIndex:', parameterIndex); // 参数索引
}

class Example {
  public greet(@ParameterDecorator message: string): void {
    console.log(`Hello, ${message}!`);
  }

  constructor(@ParameterDecorator public id: number) {}
}

输出结果

参数装饰器被调用
target: {}
propertyKey: greet
parameterIndex: 0

参数装饰器被调用
target: [class Example]
propertyKey: undefined
parameterIndex: 0

访问器装饰器

访问器装饰器应用于 getter 或 setter,可以用来修改访问器的行为

访问器装饰器接收三个参数:类的原型对象、访问器名和属性描述符:

function AccessorDecorator(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor,
) {
  console.log('访问器装饰器被调用');
  console.log('target:', target);
  console.log('propertyKey:', propertyKey);
  console.log('descriptor:', descriptor);
}

class Example {
  private _age: number = 0;

  @AccessorDecorator
  get age(): number {
    return this._age;
  }

  set age(value: number) {
    this._age = value;
  }
}

输出结果

访问器装饰器被调用
target: {}
propertyKey: age
descriptor: {
  get: [Function: get age],
  set: [Function: set age],
  enumerable: false,
  configurable: true
}

有参装饰器

有参装饰器(装饰器工厂)是一个返回装饰器函数的函数,可以自定义装饰器的行为

有参装饰器允许我们传递参数来配置装饰器的行为:

function ClassDecoratorWithParams(param: string) {
  return function(target: Function) {
    console.log('ClassDecoratorWithParams called with', param);
    target.prototype.param = param;
  };
}

@ClassDecoratorWithParams('Hello')
class MyClass {
  constructor() {}
}

const instance = new MyClass();
console.log(instance.param); // 输出: Hello

输出结果

ClassDecoratorWithParams called with Hello
Hello

装饰器的执行顺序

装饰器的执行顺序遵循严格的规则:从内到外、从下到上的层级结构

执行顺序规则

  1. 实例成员

  • 首先应用参数装饰器

  • 然后应用方法、访问器或属性装饰器

  1. 静态成员

  • 首先应用参数装饰器

  • 然后应用方法、访问器或属性装饰器

  1. 构造函数

  • 应用参数装饰器

  • 最后应用类装饰器

代码示例验证

@ClassDecorator
class Example {
  constructor(@ParameterDecorator public id: number) {
    console.log('构造函数被调用');
  }
  
  @PropertyDecorator
  public name: string = 'test';

  private _age: number = 0;

  @AccessorDecorator
  get age(): number {
    return this._age;
  }

  set age(value: number) {
    this._age = value;
  }

  @MethodDecorator
  public greet(@ParameterDecorator message: string): void {
    console.log(`Hello, ${message}!`);
  }
}

输出结果

属性装饰器被调用
target: {}
propertyKey: name

访问器装饰器被调用
target: {}
propertyKey: age
descriptor: { get: [Function: get age], set: [Function: set age], enumerable: false, configurable: true }

参数装饰器被调用
target: {}
propertyKey: greet
parameterIndex: 0

方法装饰器被调用
target: {}
propertyKey: greet
descriptor: { value: [Function: greet], writable: true, enumerable: false, configurable: true }

参数装饰器被调用
target: [class Example]
propertyKey: undefined
parameterIndex: 0

类装饰器被调用
target: [class Example]

执行顺序解析

从输出结果可以看出,装饰器的执行顺序是:

  1. 属性装饰器:修饰 name 属性

  2. 访问器装饰器:修饰 age 访问器

  3. 方法参数装饰器:修饰 greet 方法的参数

  4. 方法装饰器:修饰 greet 方法

  5. 构造函数参数装饰器:修饰构造函数的参数

  6. 类装饰器:修饰整个类

Nestjs的装饰器

Nestjs 提供了一些内置的装饰器,用于简化开发过程。装饰器的名称以 @ 开头,例如 @Controller、@Get、@Post 等。框架内部会根据装饰器的类型和位置,自动应用相应的行为。主要的有参装饰器基于Reflect-metadata模块实现。

控制器装饰器

控制器装饰器用于标记一个类作为控制器,处理 HTTP 请求

控制器装饰器可以指定控制器的基础路径:

import { Controller } from '@nestjs/common';

@Controller('users')
export class UsersController {
  // 控制器方法
}

参数说明

  • path(可选):控制器的基础路径,所有路由都会基于这个路径

路由装饰器

路由装饰器用于定义 HTTP 请求的处理方法,包括 @Get、@Post、@Put、@Delete 等

@Get 装饰器

import { Controller, Get } from '@nestjs/common';

@Controller('users')
export class UsersController {
  @Get()
  findAll() {
    return 'This action returns all users';
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return `This action returns a #${id} user`;
  }
}

@Post 装饰器

import { Controller, Post } from '@nestjs/common';

@Controller('users')
export class UsersController {
  @Post()
  create() {
    return 'This action adds a new user';
  }
}

其他路由装饰器

  • @Put():处理 PUT 请求

  • @Delete():处理 DELETE 请求

  • @Patch():处理 PATCH 请求

  • @Options():处理 OPTIONS 请求

  • @Head():处理 HEAD 请求

依赖注入装饰器

依赖注入装饰器用于标记可注入的服务和注入依赖项

@Injectable 装饰器

import { Injectable } from '@nestjs/common';

@Injectable()
export class UsersService {
  findAll() {
    return ['user1', 'user2'];
  }
}

@Inject 装饰器

import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class UsersService {
  constructor(
    @Inject('DATABASE_CONNECTION') private databaseConnection: any
  ) {}
}

模块装饰器

模块装饰器用于组织应用程序的结构,定义模块的控制器、提供者和依赖关系

import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService]
})
export class UsersModule {}

参数说明

  • controllers:模块中包含的控制器

  • providers:模块中包含的提供者(服务)

  • exports:模块导出的提供者,供其他模块使用

  • imports:模块依赖的其他模块

实现一个Nestjs @Get 装饰器

import axios from 'axios';

const Get = (url: string): MethodDecorator => {
  // 有参装饰器实质是通过高阶函数实现的
  return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
    const func = descriptor.value;
    axios
      .get(url)
      .then((res: any) => {
        func(res.data, { success: true });
      })
      .catch((err: any) => {
        func({}, { success: false });
      });
  };
};

class Controller {
  @Get('https://jsonplaceholder.typicode.com/users')
  getList(list: any[], { success }: { success: boolean }) {
    console.log(list);
    return list;
  }
}

实现一个Get装饰器,用于从指定的URL获取数据并返回给调用者,返回结果如下:

[
  {
    id: 1,
    name: "Leanne Graham",
    username: "Bret",
    email: "Sincere@april.biz",
    address: {
      street: "Kulas Light",
      suite: "Apt. 556",
      city: "Gwenborough",
      zipcode: "92998-3874",
      geo: { lat: "-37.3159", lng: "81.1496" }
    },
    phone: "1-770-736-8031 x56442",
    website: "hildegard.org",
    company: {
      name: "Romaguera-Crona",
      catchPhrase: "Multi-layered client-server neural-net",
      bs: "harness real-time e-markets"
    }
  },
  ...
]

总结

装饰器是 Nestjs 中用于定义应用程序行为的元数据机制,它们可以在类、方法、属性、参数、访问器等位置应用,以实现自定义的行为和功能。