Menu

Декораторы в TypeScript: метода, класса, поля и accessor

Декораторы это функции, которые оборачивают или заменяют члены класса через синтаксис @. Стандартные декораторы, которые TypeScript поддерживает без флагов (класса, метода, геттера, поля и accessor), фабрики декораторов, addInitializer и чем они отличаются от устаревших experimentalDecorators, на которых построены Angular и NestJS.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Декоратор это функция, которую вы прикрепляете к классу или члену класса через @name. Она получает исходный метод (или класс, или поле) и объект контекста, который его описывает, и может вернуть замену. TypeScript поддерживает стандартные декораторы без флагов компилятора.

@logged выполняется один раз, при определении класса, и заменяет add обёрткой, которую возвращает. Каждый вызов проходит через обёртку. Обобщённые параметры сохраняют типы this, аргументов и возвращаемого значения метода, так что add по-прежнему принимает два числа и возвращает число.

Как компилируются декораторы

Стандартные декораторы происходят из предложения TC39 для JavaScript, и TypeScript реализует их с версии TypeScript 5.0. Предложение ещё не входит в стандарт JavaScript, а Node 24 не разбирает синтаксис @, поэтому при цели ES2022 (как на этих страницах) компилятор переписывает каждый декорированный класс в обычный JavaScript, который вызывает вспомогательные функции (__esDecorate и __runInitializers, выводятся в начале файла). Результат работает везде, где работает ES2022.

Одно следствие: файл с декораторами нельзя запустить через встроенное удаление типов в Node (node file.ts), которое только убирает типы и оставляет @ на месте. Node останавливается с SyntaxError: Invalid or unexpected token. Сначала скомпилируйте файл через tsc или бандлер.

Виды декораторов и их сигнатуры

У каждого стандартного декоратора форма (value, context) => replacement | void. Что такое value и что можно вернуть, зависит от того, что декорируется:

ДекорируетvalueТип контекстаВозвращает
классклассClassDecoratorContextкласс на замену или ничего
методметодClassMethodDecoratorContextметод на замену
геттер / сеттергеттер или сеттерClassGetterDecoratorContext / ClassSetterDecoratorContextгеттер или сеттер на замену
полеundefinedClassFieldDecoratorContextфункцию, преобразующую начальное значение
поле accessor{ get, set }ClassAccessorDecoratorContext{ get?, set?, init? }

У каждого объекта контекста есть kind, name и addInitializer. У контекста члена класса также есть static, private и объект access для чтения члена из экземпляра. Декораторы работают и со статическими членами, и с членами #private.

Фабрики декораторов

Чтобы передать параметры, напишите функцию, которая возвращает декоратор, и вызовите её в месте @. Это фабрика декораторов:

@retry(3) сначала вызывает retry, а функция, которую она возвращает, и есть настоящий декоратор. Несколько декораторов складываются: в @a @b method() сначала применяется b, а a оборачивает результат.

Декораторы класса и addInitializer

Декоратор класса получает сам класс. Он может вернуть подкласс на замену или ничего не возвращать, а просто где-то его зарегистрировать. context.addInitializer регистрирует код, который выполнится в определённый момент: для декоратора класса сразу после полного определения класса, для декоратора метода при создании каждого экземпляра.

Декоратор класса ставится над классом (или после export). Без @bound вызов loose() выбросил бы ошибку, потому что this был бы undefined.

Декораторы полей и accessor

Декоратор поля не видит и не может перехватить последующие присваивания: его value равно undefined, и вернуть он может только функцию, которая преобразует начальное значение поля. Чтобы перехватывать чтение и запись, объявите поле с ключевым словом accessor, которое превращает его в пару геттера и сеттера с приватным хранилищем, и декорируйте его:

accessor входит в то же предложение. Он генерирует настоящие геттер и сеттер поверх поля #private, поэтому p.price = -5 проходит через set декоратора.

Стандартные декораторы и устаревшие experimentalDecorators

До TypeScript 5.0 единственными декораторами в TypeScript была ранняя версия предложения, включаемая через experimentalDecorators. Этот флаг всё ещё существует и переключает компилятор на устаревшую модель с другими сигнатурами и семантикой:

Стандартные (без флага)Устаревшие (experimentalDecorators)
Сигнатура(value, context)(target, propertyKey, descriptor)
Как меняет методвозвращает новую функциюизменяет descriptor.value
Декораторы параметровне поддерживаются (TS1206)поддерживаются
emitDecoratorMetadataне поддерживаетсяподдерживается (информация о типах во время выполнения через reflect-metadata)
Декораторы accessor ({ get, set, init }), addInitializerданет
Основапредложение TC39его ранний черновик

Декоратор, написанный для одной модели, не проходит проверку типов в другой. Вот декоратор в устаревшем стиле в проекте без флага:

index.ts(11,5): error TS1241: Unable to resolve signature of method decorator when called as an expression.
  The runtime will invoke the decorator with 2 arguments, but the decorator expects 3.

Исправить можно двумя способами: переписать его в стандартной форме (value, context), как в первом примере на этой странице, или включить устаревшую модель для всего проекта:

{
    "compilerOptions": {
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    }
}

Angular, NestJS и TypeORM по-прежнему задают experimentalDecorators в конфигурациях, которые генерируют их инструменты, и требуют его в документации. NestJS и TypeORM нужен ещё и emitDecoratorMetadata, потому что они читают типы во время выполнения: NestJS для внедрения параметров конструктора, TypeORM для сопоставления свойств с колонками:

// Legacy model: needs experimentalDecorators (and emitDecoratorMetadata for DI).
@Injectable()
class UsersService {
    constructor(@Inject(DB) private db: Database) {}
}

Если вы используете один из этих фреймворков, пишите декораторы по-старому и следуйте его документации. Для нового кода без такого фреймворка используйте стандартные декораторы.

Когда использовать декораторы

Декораторы подходят для сквозного поведения, которое иначе повторялось бы во многих методах: логирование, замер времени, кеширование, повторные попытки, проверка доступа, валидация и регистрация классов в контейнере или маршрутизаторе. При этом они скрывают поток управления: читателю нужно посмотреть, что делает @retry, прежде чем понять, что делает метод. Для одного-двух применений обычная функция высшего порядка (const fetchData = retry(3, rawFetch)) проще и работает и вне классов.

Часто задаваемые вопросы

Что такое декораторы в TypeScript?

Декоратор это функция, применяемая к классу или члену класса через синтаксис @name. Она получает декорируемую сущность и объект контекста и может вернуть замену: обёрнутый метод, новый класс или функцию, преобразующую начальное значение поля. Обычно их используют для логирования, валидации, кеширования и регистрации классов.

Нужен ли experimentalDecorators, чтобы использовать декораторы в TypeScript?

Нет. Начиная с TypeScript 5.0 стандартные декораторы (TC39) работают без флага. experimentalDecorators переключает компилятор на старую, устаревшую модель декораторов, на которой построены фреймворки вроде Angular и NestJS. У этих двух моделей разные сигнатуры функций, и они не взаимозаменяемы.

Чем стандартные декораторы отличаются от experimentalDecorators?

Стандартные декораторы получают (value, context) и возвращают замену. Устаревшие получают (target, propertyKey, descriptor) и изменяют дескриптор свойства. Только устаревшая модель поддерживает декораторы параметров и emitDecoratorMetadata; только в стандартной есть декораторы accessor, возвращающие { get, set, init }, и context.addInitializer.

Поддерживает ли TypeScript декораторы параметров?

Только с включённым experimentalDecorators. В стандартном режиме декоратор на параметре это ошибка TS1206: Decorators are not valid here, потому что предложение TC39 не включает декораторы параметров. Поэтому фреймворкам внедрения зависимостей, которые декорируют параметры конструктора, нужен устаревший флаг.

В каком порядке применяются несколько декораторов?

Выражения декораторов вычисляются сверху вниз, а применяются снизу вверх: в @a @b method() сначала b оборачивает метод, а затем a оборачивает результат. Поэтому при вызове метода a выполняется снаружи.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ