Декораторы TypeScript
Декоратор — это особая функция, которую вы прикрепляете к классу, методу, свойству или аксессору с помощью синтаксиса @name. Она выполняется, когда класс определяется, и может добавить метаданные, изменить или заменить то, что декорирует.
Декораторы активно применяются во фреймворках, таких как Angular и NestJS, для внедрения зависимостей, маршрутизации и многого другого.
Включение декораторов
Современный TypeScript (5.0 и новее) поддерживает стандартный (TC39) синтаксис декораторов сразу — никакая опция компилятора не нужна, и именно эта форма используется во всех примерах на этой странице.
Старый код использует устаревшую (legacy) схему декораторов. Чтобы скомпилировать такую форму, её нужно включить в tsconfig.json:
"compilerOptions": {"target": "ES2020","module": "commonjs","experimentalDecorators": true,"emitDecoratorMetadata": true,"strictPropertyInitialization": false},"include": ["src/**/*.ts"]
Примечание: emitDecoratorMetadata заставляет компилятор выдавать информацию о типах для устаревших декораторов. Библиотеки, такие как TypeORM и class-validator, опираются на неё.
Виды декораторов
TypeScript позволяет декорировать объявления нескольких видов. Со стандартными декораторами каждый декоратор получает два аргумента: значение, которое декорируется, и объект контекста, который сообщает его вид и имя.
- Декоратор класса — применяется к объявлениям классов:
(constructor: Function, context: ClassDecoratorContext) => void | Function - Декоратор метода — применяется к методам класса:
(method: Function, context: ClassMethodDecoratorContext) => void | Function - Декоратор аксессора — применяется к полям
accessor(стандартный способ перехватывать чтение и запись свойства):(target: { get, set }, context: ClassAccessorDecoratorContext) => { get?, set?, init? } - Декоратор поля — применяется к обычным свойствам класса:
(value: undefined, context: ClassFieldDecoratorContext) => void | ((initialValue) => any)
Устаревшая схема вместо этого передаёт (target, propertyKey, descriptor) декораторам методов, (target, propertyKey) — декораторам свойств и (target, propertyKey, parameterIndex) — декораторам параметров. Декораторы параметров существуют только в устаревшей схеме.
Декораторы классов
Декоратор класса получает конструктор класса. С его помощью можно наблюдать за классом, изменить его или даже вернуть новый конструктор, который его заменит.
Он вызывается один раз, когда класс объявляется, — а не при каждом создании экземпляра.
Простой декоратор класса
Этот простой декоратор выводит сообщение в тот момент, когда класс определяется.
logClass, который принимает конструктор класса и ClassDecoratorContext и выводит Класс <name> определён в <метка времени ISO>. Примените его с помощью @logClass к классу UserService с методом getUsers(), возвращающим ["Alice", "Bob", "Charlie"]. Больше ничего не вызывается — строка журнала появляется, как только файл загружается.
Вывод: программа на TypeScript и захваченный вывод
// Простой декоратор класса, который сообщает об определении класса
function logClass(constructor: Function, context: ClassDecoratorContext) {
console.log(`Класс ${constructor.name} определён в ${new Date().toISOString()}`);
}
// Применяем декоратор
@logClass
class UserService {
getUsers() {
return ['Alice', 'Bob', 'Charlie'];
}
}
// Вывод при загрузке файла: "Класс UserService определён в [метка времени]"
Декоратор класса с изменением конструктора
Этот пример изменяет класс: он добавляет свойство version и возвращает конструктор-обёртку, который выводит сообщение при каждом создании экземпляра.
versioned(version: string), возвращающую декоратор класса, который задаёт prototype.version, а затем возвращает конструктор-обёртку, выводящий Создание экземпляра <name> v<version> и вызывающий оригинал через new, копируя прототип, чтобы instanceof продолжал работать. Примените @versioned("1.0.0") к классу ApiClient с методом fetchData(), который выводит Получение данных...; создайте экземпляр, выведите его version (приведя к any) и вызовите fetchData().
Вывод: программа на TypeScript и захваченный вывод
// Декоратор, который добавляет свойство version и сообщает о создании экземпляра
function versioned(version: string) {
return function (constructor: Function, context: ClassDecoratorContext) {
// Добавляем статическое свойство
constructor.prototype.version = version;
// Сохраняем исходный конструктор
const original: any = constructor;
// Создаём новый конструктор, который оборачивает исходный
const newConstructor: any = function (...args: any[]) {
console.log(`Создание экземпляра ${original.name} v${version}`);
return new original(...args);
};
// Копируем прототип, чтобы instanceof работал
newConstructor.prototype = original.prototype;
return newConstructor;
};
}
// Применяем декоратор с версией
@versioned('1.0.0')
class ApiClient {
fetchData() {
console.log('Получение данных...');
}
}
const client = new ApiClient();
console.log((client as any).version); // Выводит: 1.0.0
client.fetchData();
Декоратор запечатывания класса
Этот декоратор запрещает добавлять новые свойства к классу и помечает существующие как неконфигурируемые.
sealed, который выводит Запечатывание <name>... и вызывает Object.seal и для конструктора, и для его прототипа. Примените его с помощью @sealed к классу Greeter, который хранит greeting: string, задаваемое в конструкторе, и имеет метод greet(), возвращающий Привет, <greeting>. Оставьте закомментированную строку, показывающую, что добавление Greeter.prototype.newMethod завершилось бы ошибкой.
Вывод: программа на TypeScript и захваченный вывод
function sealed(constructor: Function, context: ClassDecoratorContext) {
console.log(`Запечатывание ${constructor.name}...`);
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return `Привет, ${this.greeting}`;
}
}
// В строгом режиме это вызовет ошибку
// Greeter.prototype.newMethod = function() {}; // Ошибка: невозможно добавить свойство newMethod
Декораторы методов
Декоратор метода ставится на метод и может наблюдать за ним, изменить или заменить его.
Он получает два параметра:
originalMethod: сама функция методаcontext: объектClassMethodDecoratorContextс именем методаname, признакамиstaticиprivate, а также хукомaddInitializer
Если декоратор возвращает функцию, эта функция заменяет метод. Такая обёртка вокруг оригинала — это и есть способ добавить логирование, измерение времени или проверки вокруг вызова.
Главное о декораторах методов:
- Они выполняются, когда метод определяется, а не когда он вызывается
- Они могут изменить поведение метода, обернув его дополнительной логикой
- Их используют для сквозной функциональности: логирования, проверки данных и авторизации
- Они должны вернуть новую функцию или
undefined(когда ничего не заменяется)
Декоратор измерения времени метода
Этот декоратор измеряет и выводит, сколько времени занимает выполнение метода.
measureTime(originalMethod, context: ClassMethodDecoratorContext), возвращающую обёртку, которая запоминает performance.now() до и после вызова оригинала и выводит <method> выполнен за <ms> мс с двумя знаками после запятой. Примените его к processData(data: number[]): number[] в классе DataProcessor, который выполняет 100 000 000 итераций цикла, имитируя работу, и возвращает каждое значение удвоенным; вызовите его с [1, 2, 3, 4, 5].
Вывод: программа на TypeScript и захваченный вывод
// Декоратор метода для измерения времени выполнения
function measureTime(
originalMethod: any,
context: ClassMethodDecoratorContext
) {
const propertyKey = String(context.name);
return function (this: any, ...args: any[]) {
const start = performance.now();
const result = originalMethod.apply(this, args);
const end = performance.now();
console.log(`${propertyKey} выполнен за ${(end - start).toFixed(2)} мс`);
return result;
};
}
// Используем декоратор
class DataProcessor {
@measureTime
processData(data: number[]): number[] {
// Имитируем время обработки
for (let i = 0; i < 100000000; i++) { /* обработка */ }
return data.map(x => x * 2);
}
}
// При вызове будет выведено время выполнения
const processor = new DataProcessor();
processor.processData([1, 2, 3, 4, 5]);
Декоратор авторизации метода
Этот пример реализует управление доступом на основе ролей: декоратор проверяет роли текущего пользователя, прежде чем разрешить выполнение метода.
type UserRole = "admin" | "editor" | "viewer" и объект currentUser (id 1, имя John Doe, роли ["viewer"]). Напишите AllowedRoles(...roles), возвращающую декоратор метода, обёртка которого выбрасывает Пользователь <name> не имеет права вызывать <method>, когда у текущего пользователя нет ни одной из этих ролей. В классе DocumentService разрешите deleteDocument для admin/editor, а viewDocument — для всех трёх ролей; внутри try/catch вызовите сначала просмотр, затем удаление от имени viewer (второй вызов выбрасывает ошибку, и её сообщение печатается через console.error), затем смените роли на ["admin"] и удалите снова, уже успешно.
Вывод: программа на TypeScript и захваченный вывод
// Роли пользователей
type UserRole = 'admin' | 'editor' | 'viewer';
// Контекст текущего пользователя (упрощённый)
const currentUser = {
id: 1,
name: 'John Doe',
roles: ['viewer'] as UserRole[]
};
// Фабрика декораторов для управления доступом на основе ролей
function AllowedRoles(...allowedRoles: UserRole[]) {
return function (
originalMethod: any,
context: ClassMethodDecoratorContext
) {
const propertyKey = String(context.name);
return function (this: any, ...args: any[]) {
const hasPermission = allowedRoles.some(role =>
currentUser.roles.includes(role)
);
if (!hasPermission) {
throw new Error(
`Пользователь ${currentUser.name} не имеет права вызывать ${propertyKey}`
);
}
return originalMethod.apply(this, args);
};
};
}
// Используем декоратор
class DocumentService {
@AllowedRoles('admin', 'editor')
deleteDocument(id: string) {
console.log(`Документ ${id} удалён`);
}
@AllowedRoles('admin', 'editor', 'viewer')
viewDocument(id: string) {
console.log(`Просмотр документа ${id}`);
}
}
// Использование
const docService = new DocumentService();
try {
docService.viewDocument('doc123'); // Работает — роль viewer разрешена
docService.deleteDocument('doc123'); // Выбрасывает ошибку — viewer не может удалять
} catch (error) {
console.error(error.message);
}
// Меняем роль пользователя на admin
currentUser.roles = ['admin'];
docService.deleteDocument('doc123'); // Теперь работает — admin может удалять
Декоратор предупреждения об устаревании
Этот декоратор печатает предупреждение об устаревании всякий раз, когда вызывается метод, который будет удалён в будущей версии.
deprecated(message: string), возвращающую декоратор метода, обёртка которого вызывает console.warn с текстом Предупреждение: <method> устарел. <message>, а затем исходный метод. В классе PaymentService пометьте processPayment(amount, currency) с помощью @deprecated("Используйте processPaymentV2"), оставьте processPaymentV2 без декоратора и вызовите оба с аргументами 100, "USD".
Вывод: программа на TypeScript и захваченный вывод
function deprecated(message: string) {
return function (
originalMethod: any,
context: ClassMethodDecoratorContext
) {
const propertyKey = String(context.name);
return function (this: any, ...args: any[]) {
console.warn(`Предупреждение: ${propertyKey} устарел. ${message}`);
return originalMethod.apply(this, args);
};
};
}
class PaymentService {
@deprecated('Используйте processPaymentV2')
processPayment(amount: number, currency: string) {
console.log(`Обработка платежа ${amount} ${currency}`);
}
processPaymentV2(amount: number, currency: string) {
console.log(`Обработка платежа v2 ${amount} ${currency}`);
}
}
const payment = new PaymentService();
payment.processPayment(100, 'USD'); // Показывает предупреждение об устаревании
payment.processPaymentV2(100, 'USD'); // Без предупреждения
Декораторы свойств
Декораторы свойств позволяют наблюдать за чтением и записью свойства или изменять их.
Со стандартными декораторами свойство объявляется с ключевым словом accessor. Тогда декоратор получает два параметра:
target: объект со сгенерированными функциямиgetиsetcontext: объектClassAccessorDecoratorContextс именем свойстваname
Если вернуть объект с собственными get и set, они заменят аксессоры по умолчанию.
Главное о декораторах свойств:
- Они выполняются, когда класс определяется, а не когда обращаются к свойству
- Декоратор на обычном поле (без
accessor) может только скорректировать начальное значение; чтобы перехватывать чтение и запись, используйтеaccessor - В устаревшей схеме они получают
(target, propertyKey)и должны использоватьObject.defineProperty, чтобы изменить поведение - Их часто применяют для форматирования, логирования, проверки данных или метаданных
- Их можно сочетать с другими декораторами для более сложного поведения
Декоратор форматирования свойства
Этот декоратор автоматически форматирует свойство каждый раз, когда ему присваивают значение.
format(formatString: string), возвращающую декоратор аксессора, который хранит приватное value и возвращает пару get/set, где сеттер сохраняет formatString с заменой {} на новое значение. Примените @format("Привет, {}!") к accessor greeting: string в классе Greeter, присвойте greeting значение "Мир" и выведите его (ожидается Привет, Мир!).
Вывод: программа на TypeScript и захваченный вывод
// Декоратор свойства, который форматирует строковое свойство
function format(formatString: string) {
return function (target: ClassAccessorDecoratorTarget<any, string>, context: ClassAccessorDecoratorContext) {
let value: string;
const getter = () => value;
const setter = (newVal: string) => {
value = formatString.replace('{}', newVal);
};
return {
get: getter,
set: setter
};
};
}
class Greeter {
@format('Привет, {}!')
accessor greeting: string;
}
const greeter = new Greeter();
greeter.greeting = 'Мир';
console.log(greeter.greeting); // Выводит: Привет, Мир!
Декоратор логирования свойства
Этот декоратор записывает в журнал каждое чтение и каждое изменение свойства.
logProperty, который возвращает геттер, выводящий Чтение <name>: <value>, и сеттер, выводящий Запись <name> с <old> на <new>. Примените его к accessor name: string и accessor price: number класса Product, конструктор которого присваивает оба. Создайте new Product("Ноутбук", 999.99), присвойте price значение 899.99, затем выведите product.name.
Вывод: программа на TypeScript и захваченный вывод
function logProperty(target: ClassAccessorDecoratorTarget<any, any>, context: ClassAccessorDecoratorContext) {
const propertyKey = String(context.name);
let value: any;
const getter = function() {
console.log(`Чтение ${propertyKey}: ${value}`);
return value;
};
const setter = function(newVal: any) {
console.log(`Запись ${propertyKey} с ${value} на ${newVal}`);
value = newVal;
};
return {
get: getter,
set: setter
};
}
class Product {
@logProperty
accessor name: string;
@logProperty
accessor price: number;
constructor(name: string, price: number) {
this.name = name;
this.price = price;
}
}
const product = new Product('Ноутбук', 999.99);
product.price = 899.99; // Выводит: Запись price с 999.99 на 899.99
console.log(product.name); // Выводит: Чтение name: Ноутбук
Декоратор обязательного свойства
Этот декоратор следит за тем, чтобы свойству было присвоено значение до того, как его прочитают.
required, геттер которого выбрасывает Свойство <name> обязательно, когда сохранённое значение равно undefined, а сеттер просто сохраняет значение. Примените его к accessor username: string и accessor email: string класса User (с необязательным age?: number), конструктор которого присваивает оба. Создайте new User("johndoe", "john@example.com") — это работает — и оставьте закомментированные строки, показывающие, что передача undefined привела бы к ошибке. Программа ничего не печатает.
Вывод: программа на TypeScript и захваченный вывод
function required(target: ClassAccessorDecoratorTarget<any, any>, context: ClassAccessorDecoratorContext) {
const propertyKey = String(context.name);
let value: any;
const getter = function() {
if (value === undefined) {
throw new Error(`Свойство ${propertyKey} обязательно`);
}
return value;
};
const setter = function(newVal: any) {
value = newVal;
};
return {
get: getter,
set: setter
};
}
class User {
@required
accessor username: string;
@required
accessor email: string;
age?: number;
constructor(username: string, email: string) {
this.username = username;
this.email = email;
}
}
const user1 = new User('johndoe', 'john@example.com'); // Работает
// const user2 = new User(undefined, 'test@example.com'); // Выбрасывает ошибку: Свойство username обязательно
// const user2 = new User('johndoe', undefined); // Выбрасывает ошибку: Свойство email обязательно
Декораторы параметров
В устаревшей схеме декораторов можно также декорировать отдельный параметр конструктора или метода. Такой декоратор получает три параметра:
target: прототип класса (для методов экземпляра) или конструктор (для статических методов)propertyKey: имя метода (илиundefinedдля параметров конструктора)parameterIndex: позиция параметра в списке параметров
В стандартных декораторах декораторов параметров нет. Пример ниже сохраняет идею — описать правило для каждого параметра, а затем проверять аргументы при вызове метода, — но передаёт правила декоратору метода в порядке параметров, поэтому он компилируется с настройками по умолчанию.
Главное о декораторах параметров:
- Они выполняются, когда метод определяется, а не когда он вызывается
- Обычно их сочетают с декоратором метода, который делает основную работу
- С библиотекой
reflect-metadataони могут сохранять и читать метаданные - Они распространены во фреймворках внедрения зависимостей
- Индекс параметра позволяет найти соответствующий аргумент во время выполнения
Декоратор проверки параметров
Этот пример проверяет параметры метода: каждое правило говорит, какой тип ожидается на какой позиции, а декоратор метода проверяет аргументы при каждом вызове.
validateParam(type), которая возвращает объект правила для "string" | "number" | "boolean", и декоратор метода validate(...rules), который преобразует правила в записи { index, type } и при каждом вызове проверяет каждый аргумент (непустая строка, число, не являющееся NaN, логическое значение) и при несоответствии выбрасывает Параметр с индексом <i> не прошёл проверку типа <type>. Примените @validate(validateParam("string"), validateParam("number"), validateParam("boolean")) к createUser(name, age, isActive) в классе UserService, который выводит Создание пользователя: <name>, <age>, <isActive>; вызовите его с "John", 30, true и оставьте закомментированные вызовы, которые завершились бы ошибкой.
Вывод: программа на TypeScript и захваченный вывод
type ParamType = 'string' | 'number' | 'boolean';
// Описывает одно правило для параметра: какой тип ожидается на какой позиции
function validateParam(type: ParamType) {
return { type };
}
// Декоратор метода: правила перечислены в том же порядке, что и параметры
function validate(...rules: { type: ParamType }[]) {
return function (originalMethod: any, context: ClassMethodDecoratorContext) {
const validations: Array<{index: number, type: string}> =
rules.map((rule, index) => ({ index, type: rule.type }));
return function (this: any, ...args: any[]) {
for (const validation of validations) {
const { index, type } = validation;
const param = args[index];
let isValid = false;
switch (type) {
case 'string':
isValid = typeof param === 'string' && param.length > 0;
break;
case 'number':
isValid = typeof param === 'number' && !isNaN(param);
break;
case 'boolean':
isValid = typeof param === 'boolean';
}
if (!isValid) {
throw new Error(`Параметр с индексом ${index} не прошёл проверку типа ${type}`);
}
}
return originalMethod.apply(this, args);
};
};
}
class UserService {
@validate(validateParam('string'), validateParam('number'), validateParam('boolean'))
createUser(name: string, age: number, isActive: boolean) {
console.log(`Создание пользователя: ${name}, ${age}, ${isActive}`);
}
}
const service = new UserService();
service.createUser('John', 30, true); // Работает
// service.createUser('', 30, true); // Выбрасывает ошибку: Параметр с индексом 0 не прошёл проверку типа string
// service.createUser('John', 'thirty' as any, true); // Выбрасывает ошибку: Параметр с индексом 1 не прошёл проверку типа number
Фабрики декораторов
Фабрика декораторов — это функция, которая возвращает декоратор.
Поскольку фабрику вы вызываете с аргументами, декоратор, который она создаёт, можно настроить. Это делает декораторы гибче и пригоднее для повторного использования.
Настраиваемый декоратор логирования
Этот пример строит декоратор логирования, который можно настроить уровнем журнала и сообщением.
logWithConfig(config: { level: "log" | "warn" | "error", message?: string }), возвращающую декоратор метода, обёртка которого печатает <message>: <method> вместе с аргументами через console[level], выполняет исходный метод, а затем печатает <method> завершён. Примените его с уровнем log и сообщением Обработка платежа к processPayment(amount: number) в классе PaymentService, который выводит Обработка платежа на $<amount>. Только объявите класс — ничего не вызывается, поэтому вывода нет.
Вывод: программа на TypeScript и захваченный вывод
// Фабрика декораторов, которая принимает конфигурацию
function logWithConfig(config: { level: 'log' | 'warn' | 'error', message?: string }) {
return function (originalMethod: any, context: ClassMethodDecoratorContext) {
const propertyKey = String(context.name);
return function (this: any, ...args: any[]) {
const { level = 'log', message = 'Выполнение метода' } = config;
console[level](`${message}: ${propertyKey}`, { arguments: args });
const result = originalMethod.apply(this, args);
console[level](`${propertyKey} завершён`);
return result;
};
};
}
class PaymentService {
@logWithConfig({ level: 'log', message: 'Обработка платежа' })
processPayment(amount: number) {
console.log(`Обработка платежа на $${amount}`);
}
}
Порядок вычисления
Когда на одном объявлении стоит несколько декораторов, фабрики вычисляются сверху вниз, а затем возвращённые ими декораторы вызываются снизу вверх.
first() и second(), каждая из которых выводит <name>(): фабрика вычислена и возвращает декоратор метода, выводящий <name>(): вызван. Поставьте @first() над @second() на пустом методе method() в классе ExampleClass. Ожидаемый порядок: обе фабрики сверху вниз, затем декораторы снизу вверх (second(): вызван перед first(): вызван).
Вывод: программа на TypeScript и захваченный вывод
function first() {
console.log('first(): фабрика вычислена');
return function (originalMethod: any, context: ClassMethodDecoratorContext) {
console.log('first(): вызван');
};
}
function second() {
console.log('second(): фабрика вычислена');
return function (originalMethod: any, context: ClassMethodDecoratorContext) {
console.log('second(): вызван');
};
}
class ExampleClass {
@first()
@second()
method() {}
}
// Вывод:
// first(): фабрика вычислена
// second(): фабрика вычислена
// second(): вызван
// first(): вызван
Примеры из реальной практики
API-контроллер с декораторами
В этом примере декораторы описывают небольшой API-контроллер, похоже на то, как это делают NestJS или фреймворки на основе Express.
ROUTES. Напишите декоратор класса Controller(prefix), который сохраняет префикс в прототипе и назначает класс собранным до этого момента маршрутам, и декоратор метода Get(path), который добавляет { method: "get", path, handler }. Декорируйте UserController с помощью @Controller("/users") и два метода @Get("/") getAllUsers() и @Get("/:id") getUserById(id). Функция registerRoutes() выводит Зарегистрирован GET <prefix><path> для каждого маршрута.
Вывод: программа на TypeScript и захваченный вывод
// Простые реализации декораторов (упрощены для примера)
const ROUTES: any[] = [];
function Controller(prefix: string = '') {
return function (constructor: Function, context: ClassDecoratorContext) {
constructor.prototype.prefix = prefix;
// Декораторы методов выполняются раньше декоратора класса, поэтому
// собранные до этого момента маршруты без цели принадлежат этому классу
ROUTES.forEach(route => {
if (!route.target) {
route.target = constructor;
}
});
};
}
function Get(path: string = '') {
return function (originalMethod: any, context: ClassMethodDecoratorContext) {
ROUTES.push({
method: 'get',
path,
handler: originalMethod,
target: undefined
});
};
}
// Используем декораторы
@Controller('/users')
class UserController {
@Get('/')
getAllUsers() {
return { users: [{ id: 1, name: 'John' }] };
}
@Get('/:id')
getUserById(id: string) {
return { id, name: 'John' };
}
}
// Имитируем регистрацию маршрутов
function registerRoutes() {
ROUTES.forEach(route => {
const prefix = route.target.prototype.prefix || '';
console.log(`Зарегистрирован ${route.method.toUpperCase()} ${prefix}${route.path}`);
});
}
registerRoutes();
// Вывод:
// Зарегистрирован GET /users/
// Зарегистрирован GET /users/:id
Лучшие практики
Придерживайтесь этих правил, когда пишете декораторы:
- Делайте декораторы узкими: каждый декоратор должен делать одну вещь.
- Описывайте поведение: ясно говорите, что делает декоратор и какие у него побочные эффекты.
- Используйте фабрики декораторов: делайте декораторы настраиваемыми, чтобы их можно было применять повторно.
- Учитывайте производительность: декоратор, который оборачивает метод, добавляет работу к каждому вызову.
- Безопасность типов: используйте типы контекста (
ClassMethodDecoratorContextи подобные), чтобы декораторы оставались типобезопасными. - Обработка ошибок: корректно обрабатывайте ошибки внутри декораторов.
- Тестирование: пишите модульные тесты для своих декораторов.
- Метаданные: используйте
reflect-metadataдля сложных случаев, где нужна информация о типах во время выполнения.
Частые ошибки
Избегайте этих распространённых ошибок:
- Не та разновидность декораторов: декораторам в устаревшем стиле нужен
experimentalDecoratorsвtsconfig.json; у стандартных декораторов его быть не должно. - Неверная сигнатура декоратора: у каждого вида декораторов свои параметры. Неправильные приводят к ошибкам во время выполнения.
- Порядок вычисления: составленные друг над другом декораторы применяются снизу вверх.
- Инициализация свойств: декораторы свойств выполняются до того, как инициализируются свойства экземпляра.
- Отражение метаданных: забытый импорт
reflect-metadata, когда вы используете метаданные декораторов. - Накладные расходы: будьте осторожны с тяжёлыми декораторами в коде, критичном к производительности.
- Совместимость с браузерами: декораторы — это предложение стадии 3, поэтому старым браузерам нужен транспилированный код.
Два фрагмента ниже используют устаревший синтаксис. Чтобы их скомпилировать, добавьте в tsconfig.json: "compilerOptions": { "experimentalDecorators": true }.
Где используются декораторы?
- Angular: компоненты, сервисы, модули и другое
- NestJS: контроллеры, провайдеры, маршруты и другое
Устаревший декоратор свойства readonly
Декоратор свойства в устаревшем стиле, который с помощью Object.defineProperty делает name недоступным для записи.
function readonly(target: any, propertyKey: string) {
Object.defineProperty(target, propertyKey, {
writable: false
});
}
class Person {
@readonly
name = "John";
}
Устаревший декоратор параметра
Декоратор параметра в устаревшем стиле, который выводит имя метода и индекс декорированного параметра.
function logParameter(target: any, propertyKey: string, parameterIndex: number) {
console.log(`Parameter in ${propertyKey} at index ${parameterIndex}`);
}
class Demo {
greet(@logParameter message: string) {
return message;
}
}