Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Поддерживают ли декораторы только методы?
Нет, декораторы поддерживают не только методы. Это важное уточнение для полного понимания декораторов в TypeScript и JavaScript.
Типы целей для декораторов
Декораторы можно применять к:
- Классам (class decorators)
- Методам (method decorators)
- Свойствам/полям (property decorators)
- Аксессорам (accessor decorators) — get/set
- Параметрам (parameter decorators)
1. Класс декоратор
// Самый простой и часто используемый
function Entity(target: Function) {
console.log(`Decorating ${target.name}`);
target.prototype.entity = true;
}
@Entity
class User {
id: number;
name: string;
}
const user = new User();
console.log(user.entity); // true
Практический пример: добавить metadata
function TableName(name: string) {
return function(target: Function) {
Reflect.defineMetadata('table', name, target);
};
}
@TableName('users')
class User {
id: number;
name: string;
}
const tableName = Reflect.getMetadata('table', User);
console.log(tableName); // "users"
2. Метод декоратор
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey} with args:`, args);
const result = originalMethod.apply(this, args);
console.log(`Result:`, 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);
// Output:
// Calling add with args: [2, 3]
// Result: 5
Практический пример: обработка ошибок
function HandleErrors(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
try {
return await originalMethod.apply(this, args);
} catch (error) {
console.error(`Error in ${propertyKey}:`, error);
throw error;
}
};
return descriptor;
}
class UserService {
@HandleErrors
async getUser(id: number) {
// может выбросить ошибку
return { id, name: 'John' };
}
}
3. Свойство декоратор
function Validate(target: any, propertyKey: string) {
let value: any;
const getter = function() {
return value;
};
const setter = function(newValue: any) {
if (typeof newValue !== 'string') {
throw new TypeError(`${propertyKey} must be a string`);
}
value = newValue;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true,
});
}
class User {
@Validate
name: string;
constructor(name: string) {
this.name = name; // Проходит через setter
}
}
const user = new User('John');
user.name = 123; // TypeError: name must be a string
Практический пример: метаданные для ORM
function Column(type: string) {
return function(target: any, propertyKey: string) {
if (!target.columns) {
target.columns = {};
}
target.columns[propertyKey] = type;
};
}
class User {
@Column('VARCHAR(255)')
name: string;
@Column('INT')
age: number;
@Column('VARCHAR(255)')
email: string;
}
console.log(User.columns);
// { name: 'VARCHAR(255)', age: 'INT', email: 'VARCHAR(255)' }
4. Аксессор декоратор (Getter/Setter)
function MinLength(length: number) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const setter = descriptor.set;
descriptor.set = function(value: any) {
if (value.length < length) {
throw new Error(`${propertyKey} must be at least ${length} characters`);
}
setter.call(this, value);
};
return descriptor;
};
}
class User {
private _password: string;
@MinLength(8)
set password(value: string) {
this._password = value;
}
get password(): string {
return this._password;
}
}
const user = new User();
user.password = 'short'; // Error: password must be at least 8 characters
5. Параметр декоратор
function Required(target: any, propertyKey: string | symbol, parameterIndex: number) {
const existingRequiredParameters: number[] =
Reflect.getOwnMetadata('required', target, propertyKey) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata('required', existingRequiredParameters, target, propertyKey);
}
class UserService {
getUser(@Required id: number, name?: string) {
return { id, name };
}
}
Практический пример с reflect-metadata:
import 'reflect-metadata';
function Validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const paramTypes = Reflect.getMetadata('design:paramtypes', target, propertyKey);
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
args.forEach((arg, index) => {
const expectedType = paramTypes[index];
if (!(arg instanceof expectedType)) {
throw new TypeError(
`Parameter ${index} should be of type ${expectedType.name}`
);
}
});
return originalMethod.apply(this, args);
};
return descriptor;
}
class UserService {
@Validate
getUser(id: number): User {
return { id, name: 'John' };
}
}
NestJS примеры (самые популярные)
// Декоратор контроллера (класс)
@Controller('users')
export class UserController { }
// Декоратор маршрута (метод)
@Get(':id')
getUser(@Param('id') id: string) { }
// Декоратор параметра
getUser(@Param('id') id: string) { }
// Декоратор тела
creatUser(@Body() dto: CreateUserDto) { }
// Декоратор property (для инъекции зависимостей)
@Injectable()
class UserService {
@Inject()
private repository: UserRepository;
}
Порядок применения декораторов
Важно понимать, что декораторы вычисляются снизу вверх, но применяются сверху вниз:
function First() {
console.log('First evaluated');
return (target: any) => console.log('First applied');
}
function Second() {
console.log('Second evaluated');
return (target: any) => console.log('Second applied');
}
@First
@Second
class Example {}
// Output:
// Second evaluated
// First evaluated
// First applied
// Second applied
Таблица целей декораторов
| Тип | Сигнатура | Пример | Когда использовать |
|---|---|---|---|
| Class | (constructor: Function) | @Entity | Metadata, наследование |
| Method | (target, propertyKey, descriptor) | @Log, @Deprecated | Логирование, кеширование |
| Property | (target, propertyKey) | @Column, @Validate | Валидация, метаданные |
| Accessor | (target, propertyKey, descriptor) | @MinLength на getter/setter | Валидация свойств |
| Parameter | (target, propertyKey, parameterIndex) | @Required, @Inject | Валидация параметров, DI |
Реальный пример: полноценный ORM декоратор
function Entity(tableName: string) {
return function(constructor: Function) {
Reflect.defineMetadata('table', tableName, constructor);
};
}
function Column(options?: { type?: string; nullable?: boolean }) {
return function(target: any, propertyKey: string) {
const columns = Reflect.getMetadata('columns', target) || {};
columns[propertyKey] = options || {};
Reflect.defineMetadata('columns', columns, target);
};
}
@Entity('users')
class User {
@Column({ type: 'INT', nullable: false })
id: number;
@Column({ type: 'VARCHAR(255)', nullable: false })
name: string;
@Column({ type: 'VARCHAR(255)', nullable: true })
email?: string;
}
const table = Reflect.getMetadata('table', User);
const columns = Reflect.getMetadata('columns', new User());
console.log(table); // "users"
console.log(columns); // { id: {...}, name: {...}, email: {...} }
Выводы
- Декораторы — это мощный инструмент, поддерживающий 5 типов целей
- Классы — декорируются редко, используются для metadata
- Методы — самые часто используемые (логирование, обработка ошибок)
- Свойства — для валидации и преобразований
- Параметры — для DI и валидации параметров
- NestJS максимально использует декораторы
- reflect-metadata открывает мощные возможности с метаданными
Декораторы делают код декларативным и помогают избежать дублирования логики.