Декоратор - функція, що отримує елемент класу (метод, поле, аксесор чи весь клас) і змінює чи доповнює його поведінку. Записується через @ перед оголошенням:
function logged(method, context) {
return function (...args) {
console.log(`виклик ${String(context.name)}`, args);
return method.apply(this, args);
};
}
class OrderService {
@logged
place(order) { /* ... */ }
}
Стан стандарту. Декоратори - пропозиція TC39 на стадії 3: синтаксис і семантика узгоджені, але рушії браузерів і Node.js нативно їх ще не виконують. Використовуються через компіляцію:
- TypeScript 5.0+ підтримує стандартні декоратори без прапорців;
- Babel - через плагін.
Дві несумісні версії. Це головне джерело плутанини:
- «експериментальні» (legacy) декоратори - TypeScript з
experimentalDecorators: true. На них побудовані Angular, NestJS, TypeORM, MobX (старі версії). Вони отримують дескриптор властивості й підтримують декоратори параметрів; - стандартні декоратори (стадія 3) - інший API: отримують значення й об'єкт
context(kind,name,addInitializer,access,metadata). Декораторів параметрів у них немає.
Декоратор, написаний для однієї версії, не працює з іншою. Перехід фреймворків на стандартні декоратори - поступовий.
Що можна декорувати:
- методи - обгортки: логування, кешування, повтор при помилці, перевірка прав;
- поля - перетворення початкового значення;
- аксесори з
accessor- нове ключове слово, що створює пару гетер/сетер з прихованим сховищем. Основа для реактивних полів (Lit, MobX); - класи - реєстрація, додавання поведінки.
class Counter {
@reactive accessor count = 0; // гетер/сетер, які декоратор може перехопити
}
Метадані (окрема пропозиція, теж стадія 3): context.metadata - об'єкт, куди декоратори записують інформацію про клас. Замінює reflect-metadata, на якому побудовані DI-контейнери NestJS і Angular.
Порівняння з PHP: атрибути PHP 8 (#[Route('/orders')]) - лише метадані, які читає фреймворк через рефлексію. Декоратори JavaScript - виконуваний код, що змінює елемент у момент визначення класу.
Чи варто використовувати в застосунку: якщо фреймворк побудований на них (Angular, NestJS, Lit) - так, у тій версії, яку він вимагає. У власному коді - обережно: компіляція обов'язкова, а стандарт ще не в рушіях. Звичайна функція вищого порядку (const place = logged(placeImpl)) дає той самий ефект без нового синтаксису.