Загрузка…
Загрузка…
TypeScript · senior · сложность 7
Декоратор — функция, которая наблюдает или подменяет объявление в момент определения (один раз при объявлении класса, не на каждый инстанс). Важно: сейчас две несовместимые системы — legacy experimentalDecorators (Angular, NestJS, TypeORM) и стандарт TC39 Stage 3 (TS 5.0+, default). Сигнатуры разные, смешивать нельзя.
@logged class Foo {} ≈ Foo = logged(Foo) ?? Foo. Это не «магия на каждый вызов», а вызов, который компилятор вшивает в место декларации.
Legacy (experimentalDecorators: true): сигнатура зависит от места (class/method/accessor/property/parameter). Вместе с emitDecoratorMetadata + reflect-metadata даёт runtime type metadata — основа DI в Angular/Nest.
Stage 3 (TS 5.0+): (value, context), context.kind, addInitializer, возврат replacement. Без флага, без parameter decorators и без emitDecoratorMetadata — reflection-DI «как раньше» пока не закрывает.
Порядок: выражения декораторов сверху вниз, применение снизу вверх (как f(g(x))).
Legacy:
// Legacy method decorator: (target, propertyKey, descriptor)
function logged(_t: unknown, key: string, desc: PropertyDescriptor) {
const orig = desc.value;
desc.value = function (...args: unknown[]) {
console.log(`→ ${key}`, args);
return orig.apply(this, args);
};
}Stage 3:
// Stage 3 method decorator: (value, context)
function logged<T extends (...a: any[]) => any>(
orig: T,
ctx: ClassMethodDecoratorContext
) {
return function (this: unknown, ...args: Parameters<T>) {
console.log(`→ ${String(ctx.name)}`, args);
return orig.call(this, ...args);
} as T;
}Один и тот же intent в обоих мирах:
// Same intent, both worlds. Legacy mutates the descriptor in place:
class ApiLegacy {
@logged // experimentalDecorators: true
fetch(id: string) { /* ... */ }
}
// Stage 3 returns a replacement — no descriptor mutation, no flags:
class ApiModern {
@logged // TS 5.0 default
fetch(id: string) { /* ... */ }
}Class decorator, возвращающий subclass:
// Class decorator returning a subclass (Stage 3) — the "replace the declaration" power
function withId<T extends new (...a: any[]) => object>(Base: T, _c: ClassDecoratorContext) {
return class extends Base {
id = crypto.randomUUID();
};
}
@withId class Widget {}
// new Widget().id is present at runtime, but note: the added field
// is NOT visible on the static type unless you also declare it.Назовите, какая система декораторов в кодовой базе и почему они не сосуществуют. Legacy — для DI/metadata; Stage 3 — стандарт будущего без parameter decorators.