Что такое декораторы в TypeScript?
Декораторы в TypeScript
Декораторы — это экспериментальная функция TypeScript (и будущий стандарт ECMAScript), которая позволяет аннотировать и изменять классы и их члены с помощью специального синтаксиса @expression.
Типы декораторов
TypeScript поддерживает пять видов декораторов:
- Декораторы классов — применяются к конструктору класса
- Декораторы методов — применяются к дескриптору метода
- Декораторы свойств — применяются к свойству класса
- Декораторы параметров — применяются к параметру метода
- Декораторы геттеров/сеттеров — применяются к аксессорам
Как работают декораторы
Декоратор — это обычная функция, которая получает информацию о целевом элементе и может его модифицировать. Декораторы применяются снизу вверх, если их несколько.
// Декоратор класса
function Sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@Sealed
class BankAccount {
balance: number = 0;
}
Фабрики декораторов
Чтобы передавать аргументы в декоратор, используют фабрику — функцию, возвращающую декоратор:
function Log(prefix: string) {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`[${prefix}] Вызов ${key} с аргументами:`, args);
return original.apply(this, args);
};
return descriptor;
};
}
class UserService {
@Log('UserService')
getUser(id: number) {
return { id, name: 'Иван' };
}
}
Декоратор свойства
function Required(target: any, key: string) {
// Сохраняем метаданные о том, что поле обязательно
const requiredFields: string[] = Reflect.getMetadata('required', target) || [];
requiredFields.push(key);
Reflect.defineMetadata('required', requiredFields, target);
}
class CreateUserDto {
@Required
name: string;
@Required
email: string;
}
Порядок применения
При нескольких декораторах они вычисляются сверху вниз, а применяются снизу вверх:
@First() // вычисляется 1-м, применяется 2-м
@Second() // вычисляется 2-м, применяется 1-м
class Example {}
Reflect Metadata
Для работы с метаданными (например, в NestJS) используется пакет reflect-metadata. Он позволяет хранить и читать произвольные данные о классах и их членах.
Практическое применение
Декораторы широко используются в:
- NestJS —
@Controller(),@Injectable(),@Get() - TypeORM —
@Entity(),@Column(),@PrimaryGeneratedColumn() - Angular —
@Component(),@NgModule() - class-validator —
@IsEmail(),@IsNotEmpty()
Включение декораторов
В tsconfig.json необходимо установить:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
Декораторы стадии TC39 Stage 3
Существует новый стандарт декораторов (Stage 3), который отличается от экспериментальной версии TypeScript. TypeScript 5.0+ поддерживает оба варианта, однако большинство фреймворков пока используют экспериментальную версию.
Что хочет услышать интервьюер
Понимание того, что декоратор — это функция высшего порядка, применяемая к классу или его члену
Знание типов декораторов: классов, методов, свойств, параметров
Понимание концепции фабрики декораторов для передачи аргументов
Знакомство с реальными фреймворками, использующими декораторы (NestJS, TypeORM, Angular)
Понимание необходимости включить `experimentalDecorators` в tsconfig
Пример: Декоратор метода — логирование вызовов
function Log(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Вызов метода ${key}, аргументы:`, args);
const result = original.apply(this, args);
console.log(`Результат метода ${key}:`, 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);
// Вызов метода add, аргументы: [2, 3]
// Результат метода add: 5
Пример: Фабрика декораторов — декоратор класса с аргументом
function Component(options: { selector: string; template: string }) {
return function (constructor: Function) {
// Сохраняем мета-информацию на прототипе класса
constructor.prototype.selector = options.selector;
constructor.prototype.template = options.template;
};
}
@Component({
selector: 'app-button',
template: '<button>Нажми меня</button>',
})
class ButtonComponent {
onClick() {
console.log('Кнопка нажата');
}
}
const btn = new ButtonComponent() as any;
console.log(btn.selector); // 'app-button'
Пример: Декоратор свойства — пометка обязательных полей
// Простой вариант без reflect-metadata
const requiredMetaKey = Symbol('required');
function Required(target: any, propertyKey: string) {
const existing: string[] = target[requiredMetaKey] || [];
target[requiredMetaKey] = [...existing, propertyKey];
}
function validate(instance: any): boolean {
const required: string[] = instance[requiredMetaKey] || [];
return required.every((key) => instance[key] !== undefined && instance[key] !== '');
}
class CreateUserDto {
@Required
name: string = '';
@Required
email: string = '';
age?: number;
}
const dto = new CreateUserDto();
dto.name = 'Иван';
console.log(validate(dto)); // false — email не заполнен
dto.email = 'ivan@example.com';
console.log(validate(dto)); // true
Типичные ошибки
Путают декораторы классов и методов — не понимают разницу в сигнатурах функций
Забывают про фабрику декораторов и пытаются передать аргументы напрямую без обёртки
Не знают, что декораторы применяются снизу вверх при наличии нескольких на одном элементе
Путают экспериментальные декораторы TypeScript и новый стандарт TC39 Stage 3
Не понимают, что декораторы свойств не получают дескриптор и не могут напрямую изменить значение


