← Назад к вопросам
Реализовывал ли Decorator на TypeScript
2.0 Middle🔥 151 комментариев
#TypeScript#Архитектура и паттерны#ООП
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
TypeScript Decorators: практический опыт
Что такое Decorators
Decorator — это функция, которая модифицирует класс, метод, параметр или свойство во время компиляции. Это синтаксический сахар над паттерном Decorator из Design Patterns.
Decorators — это экспериментальная фишка TypeScript, которая станет частью ES стандарта.
Включение поддержки Decorators
// tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "ES2020"
}
}
1. Method Decorators — логирование вызовов
Это мой самый частый use case. Декоратор логирует все вызовы метода:
// src/decorators/Log.ts
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
console.log(`[${new Date().toISOString()}] Calling ${propertyKey}`);
console.log('Arguments:', args);
try {
const result = await originalMethod.apply(this, args);
console.log('Result:', result);
return result;
} catch (error) {
console.error('Error:', error);
throw error;
}
};
return descriptor;
}
export { Log };
Использование:
class UserService {
@Log
async getUserById(id: number) {
return { id, name: 'John' };
}
}
// При вызове:
// [2024-03-29T10:15:30Z] Calling getUserById
// Arguments: [ 123 ]
// Result: { id: 123, name: 'John' }
2. Class Decorators — с проверкой прав
Декоратор, который проверяет права доступа перед выполнением метода:
function Authenticate(roles: string[]) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (req: Request, ...args: any[]) {
const userRole = req.user?.role;
if (!userRole || !roles.includes(userRole)) {
throw new Error(`Access denied. Required roles: ${roles.join(', ')}`);
}
return originalMethod.apply(this, [req, ...args]);
};
return descriptor;
};
}
// Использование
class AdminController {
@Authenticate(['admin', 'moderator'])
async deleteUser(req: Request, userId: number) {
return { deleted: true, userId };
}
}
3. Кэширование результатов
Декоратор, который кэширует результаты метода:
function Cacheable(ttl: number = 3600) {
const cache = new Map<string, { value: any; timestamp: number }>();
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const cacheKey = `${propertyKey}:${JSON.stringify(args)}`;
const cached = cache.get(cacheKey);
// Проверяем, если кэш свежий
if (cached && Date.now() - cached.timestamp < ttl * 1000) {
console.log(`Cache hit for ${cacheKey}`);
return cached.value;
}
// Иначе вычисляем и кэшируем
const result = await originalMethod.apply(this, args);
cache.set(cacheKey, { value: result, timestamp: Date.now() });
return result;
};
return descriptor;
};
}
// Использование
class ProductService {
@Cacheable(3600) // Кэш на 1 час
async getProductById(id: number) {
// Дорогой запрос в БД
return await db.query('SELECT * FROM products WHERE id = $1', [id]);
}
}
4. Валидация параметров
Декоратор, который валидирует входные параметры:
function Validate(schema: any) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (data: any, ...args: any[]) {
try {
// Используем Zod или Joi для валидации
const validated = await schema.parseAsync(data);
return originalMethod.apply(this, [validated, ...args]);
} catch (error) {
throw new Error(`Validation error: ${error.message}`);
}
};
return descriptor;
};
}
// Использование с Zod
import { z } from 'zod';
const createUserSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string().min(1)
});
class UserService {
@Validate(createUserSchema)
async createUser(data: any) {
// data уже валидирован
return { id: 1, ...data };
}
}
5. Retry декоратор — для нестабильных операций
Декоратор, который повторяет вызов в случае ошибки:
function Retry(attempts: number = 3, delay: number = 1000) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
let lastError: Error;
for (let i = 0; i < attempts; i++) {
try {
console.log(`Attempt ${i + 1} of ${attempts}`);
return await originalMethod.apply(this, args);
} catch (error) {
lastError = error;
if (i < attempts - 1) {
console.log(`Waiting ${delay}ms before retry...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw lastError;
};
return descriptor;
};
}
// Использование
class ExternalApiService {
@Retry(3, 2000) // 3 попытки с интервалом 2 сек
async callUnstableApi() {
return await fetch('https://api.example.com/data');
}
}
6. Property Decorators — для валидации свойств
function Min(min: number) {
return function (target: any, propertyKey: string) {
let value: number;
const getter = () => value;
const setter = (newValue: number) => {
if (newValue < min) {
throw new Error(`${propertyKey} must be >= ${min}`);
}
value = newValue;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}
// Использование
class Product {
@Min(0)
price: number;
}
const p = new Product();
p.price = -10; // Ошибка!
Реальный пример: комбинация декораторов
class PaymentService {
@Log
@Authenticate(['admin'])
@Cacheable(300) // кэш на 5 минут для админов
@Retry(3, 1000) // повторяем 3 раза при ошибке
async processPayment(paymentData: any) {
return await paymentGateway.charge(paymentData);
}
}
Порядок выполнения:
- Log логирует вызов
- Authenticate проверяет права
- Cacheable проверяет кэш
- Retry обёртывает логику повторов
- Основной метод
Где я использую Decorators в production
- NestJS — весь фреймворк построен на декораторах
- API контроллеры — @Get(), @Post(), @Authenticate()
- Сервисы — @Cacheable(), @Log(), @Retry()
- Middleware — @UseMiddleware(), @UseGuards()
Плюсы и минусы
Плюсы:
- Чистый и читаемый код
- Разделение concerns (логирование отдельно от бизнес-логики)
- Легко переиспользовать
- Работает с TypeScript
Минусы:
- Экспериментальная фишка (может измениться)
- Overhead в runtime
- Может быть сложным для новичков
- Нужно правильно понимать порядок применения