Декораторы в TypeScript/JavaScript: применяются ли они только к методам?

«Декораторы в TypeScript/JavaScript: применяются ли они только к методам?» — вопрос из категории TypeScript, который задают на 26% собеседований Node.js Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Нет, декораторы в TypeScript (и экспериментальные декораторы в JavaScript) могут применяться к нескольким типам объявлений. В моих Node.js проектах с TypeScript я использую декораторы для:

1. Декораторы классов — для регистрации в DI-контейнере, добавления метаданных:

import { Injectable, Singleton } from 'tsyringe';

@Injectable() // Декоратор класса
@Singleton()  // Можно применять несколько декораторов
class DatabaseService {
  constructor(private config: ConfigService) {}

  async connect() {
    return await mongoose.connect(this.config.databaseUrl);
  }
}

2. Декораторы методов — для логирования, кэширования, валидации:

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

@Controller('users')
class UsersController {
  @Get() // Декоратор метода HTTP
  @UseGuards(AuthGuard) // Декоратор middleware
  @CacheTTL(60) // Декоратор кэширования
  async getAllUsers() {
    return await this.userService.findAll();
  }
}

3. Декораторы свойств — для валидации, сериализации, внедрения зависимостей:

import { Expose, Type } from 'class-transformer';
import { IsEmail, IsString } from 'class-validator';

class UserDto {
  @Expose() // Декоратор свойства для class-transformer
  id: number;

  @IsString() // Валидация
  @IsEmail()  // Можно несколько декораторов
  email: string;

  @Type(() => Date) // Преобразование типа
  createdAt: Date;
}

4. Декораторы параметров — для внедрения зависимостей в параметры конструктора/методов:

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

class UserService {
  constructor(
    @Inject('USER_MODEL') // Декоратор параметра
    private userModel: Model<User>,
    @Inject(ConfigService)
    private config: ConfigService
  ) {}
}

5. Декораторы аксессоров (геттеров/сеттеров):

class Product {
  private _price: number;

  @Min(0) // Декоратор аксессора
  get price() {
    return this._price;
  }

  set price(value: number) {
    this._price = value;
  }
}

В Node.js с TypeScript декораторы полностью поддерживаются с флагом experimentalDecorators: true в tsconfig.json. В чистом JavaScript (Node.js ≥ 14) можно использовать экспериментальные декораторы с флагом --experimental-decorators, но TypeScript предлагает более стабильную и богатую реализацию.

Практический пример из моего опыта — декоратор для логирования времени выполнения методов:

function LogExecutionTime() {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: any[]) {
      const start = Date.now();
      const result = await originalMethod.apply(this, args);
      const duration = Date.now() - start;

      console.log(`${propertyKey} executed in ${duration}ms`);
      return result;
    };

    return descriptor;
  };
}

class UserService {
  @LogExecutionTime()
  async findUsers() {
    // Длительная операция
    return await db.users.findMany();
  }
}