← Назад к вопросам

Поддерживают ли декораторы только методы

2.2 Middle🔥 131 комментариев
#TypeScript

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Поддерживают ли декораторы только методы?

Нет, декораторы поддерживают не только методы. Это важное уточнение для полного понимания декораторов в TypeScript и JavaScript.

Типы целей для декораторов

Декораторы можно применять к:

  1. Классам (class decorators)
  2. Методам (method decorators)
  3. Свойствам/полям (property decorators)
  4. Аксессорам (accessor decorators) — get/set
  5. Параметрам (parameter decorators)

1. Класс декоратор

// Самый простой и часто используемый
function Entity(target: Function) {
  console.log(`Decorating ${target.name}`);
  target.prototype.entity = true;
}

@Entity
class User {
  id: number;
  name: string;
}

const user = new User();
console.log(user.entity); // true

Практический пример: добавить metadata

function TableName(name: string) {
  return function(target: Function) {
    Reflect.defineMetadata('table', name, target);
  };
}

@TableName('users')
class User {
  id: number;
  name: string;
}

const tableName = Reflect.getMetadata('table', User);
console.log(tableName); // "users"

2. Метод декоратор

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

  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with args:`, args);
    const result = originalMethod.apply(this, args);
    console.log(`Result:`, result);
    return result;
  };

  return descriptor;
}

class Calculator {
  @Log
  add(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(2, 3);
// Output:
// Calling add with args: [2, 3]
// Result: 5

Практический пример: обработка ошибок

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

  descriptor.value = async function (...args: any[]) {
    try {
      return await originalMethod.apply(this, args);
    } catch (error) {
      console.error(`Error in ${propertyKey}:`, error);
      throw error;
    }
  };

  return descriptor;
}

class UserService {
  @HandleErrors
  async getUser(id: number) {
    // может выбросить ошибку
    return { id, name: 'John' };
  }
}

3. Свойство декоратор

function Validate(target: any, propertyKey: string) {
  let value: any;

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

  const setter = function(newValue: any) {
    if (typeof newValue !== 'string') {
      throw new TypeError(`${propertyKey} must be a string`);
    }
    value = newValue;
  };

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

class User {
  @Validate
  name: string;

  constructor(name: string) {
    this.name = name; // Проходит через setter
  }
}

const user = new User('John');
user.name = 123; // TypeError: name must be a string

Практический пример: метаданные для ORM

function Column(type: string) {
  return function(target: any, propertyKey: string) {
    if (!target.columns) {
      target.columns = {};
    }
    target.columns[propertyKey] = type;
  };
}

class User {
  @Column('VARCHAR(255)')
  name: string;

  @Column('INT')
  age: number;

  @Column('VARCHAR(255)')
  email: string;
}

console.log(User.columns);
// { name: 'VARCHAR(255)', age: 'INT', email: 'VARCHAR(255)' }

4. Аксессор декоратор (Getter/Setter)

function MinLength(length: number) {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const setter = descriptor.set;

    descriptor.set = function(value: any) {
      if (value.length < length) {
        throw new Error(`${propertyKey} must be at least ${length} characters`);
      }
      setter.call(this, value);
    };

    return descriptor;
  };
}

class User {
  private _password: string;

  @MinLength(8)
  set password(value: string) {
    this._password = value;
  }

  get password(): string {
    return this._password;
  }
}

const user = new User();
user.password = 'short'; // Error: password must be at least 8 characters

5. Параметр декоратор

function Required(target: any, propertyKey: string | symbol, parameterIndex: number) {
  const existingRequiredParameters: number[] = 
    Reflect.getOwnMetadata('required', target, propertyKey) || [];
  existingRequiredParameters.push(parameterIndex);
  Reflect.defineMetadata('required', existingRequiredParameters, target, propertyKey);
}

class UserService {
  getUser(@Required id: number, name?: string) {
    return { id, name };
  }
}

Практический пример с reflect-metadata:

import 'reflect-metadata';

function Validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const paramTypes = Reflect.getMetadata('design:paramtypes', target, propertyKey);
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    args.forEach((arg, index) => {
      const expectedType = paramTypes[index];
      if (!(arg instanceof expectedType)) {
        throw new TypeError(
          `Parameter ${index} should be of type ${expectedType.name}`
        );
      }
    });
    return originalMethod.apply(this, args);
  };

  return descriptor;
}

class UserService {
  @Validate
  getUser(id: number): User {
    return { id, name: 'John' };
  }
}

NestJS примеры (самые популярные)

// Декоратор контроллера (класс)
@Controller('users')
export class UserController { }

// Декоратор маршрута (метод)
@Get(':id')
getUser(@Param('id') id: string) { }

// Декоратор параметра
getUser(@Param('id') id: string) { }

// Декоратор тела
creatUser(@Body() dto: CreateUserDto) { }

// Декоратор property (для инъекции зависимостей)
@Injectable()
class UserService {
  @Inject()
  private repository: UserRepository;
}

Порядок применения декораторов

Важно понимать, что декораторы вычисляются снизу вверх, но применяются сверху вниз:

function First() {
  console.log('First evaluated');
  return (target: any) => console.log('First applied');
}

function Second() {
  console.log('Second evaluated');
  return (target: any) => console.log('Second applied');
}

@First
@Second
class Example {}

// Output:
// Second evaluated
// First evaluated
// First applied
// Second applied

Таблица целей декораторов

ТипСигнатураПримерКогда использовать
Class(constructor: Function)@EntityMetadata, наследование
Method(target, propertyKey, descriptor)@Log, @DeprecatedЛогирование, кеширование
Property(target, propertyKey)@Column, @ValidateВалидация, метаданные
Accessor(target, propertyKey, descriptor)@MinLength на getter/setterВалидация свойств
Parameter(target, propertyKey, parameterIndex)@Required, @InjectВалидация параметров, DI

Реальный пример: полноценный ORM декоратор

function Entity(tableName: string) {
  return function(constructor: Function) {
    Reflect.defineMetadata('table', tableName, constructor);
  };
}

function Column(options?: { type?: string; nullable?: boolean }) {
  return function(target: any, propertyKey: string) {
    const columns = Reflect.getMetadata('columns', target) || {};
    columns[propertyKey] = options || {};
    Reflect.defineMetadata('columns', columns, target);
  };
}

@Entity('users')
class User {
  @Column({ type: 'INT', nullable: false })
  id: number;

  @Column({ type: 'VARCHAR(255)', nullable: false })
  name: string;

  @Column({ type: 'VARCHAR(255)', nullable: true })
  email?: string;
}

const table = Reflect.getMetadata('table', User);
const columns = Reflect.getMetadata('columns', new User());
console.log(table); // "users"
console.log(columns); // { id: {...}, name: {...}, email: {...} }

Выводы

  • Декораторы — это мощный инструмент, поддерживающий 5 типов целей
  • Классы — декорируются редко, используются для metadata
  • Методы — самые часто используемые (логирование, обработка ошибок)
  • Свойства — для валидации и преобразований
  • Параметры — для DI и валидации параметров
  • NestJS максимально использует декораторы
  • reflect-metadata открывает мощные возможности с метаданными

Декораторы делают код декларативным и помогают избежать дублирования логики.