Decorators
Decorators are functions that wrap classes, methods, fields, or accessors with extra behaviour. TypeScript 5 implements the stage-3 spec — close to what's landing in JavaScript itself.
Method decorator
TS
function log<This, Args extends unknown[], Ret>(
target: (this: This, ...args: Args) => Ret,
ctx: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Ret>,
) {
return function (this: This, ...args: Args): Ret {
console.log(`-> ${String(ctx.name)}(${args.join(', ')})`);
return target.call(this, ...args);
};
}
class Calculator {
@log
add(a: number, b: number) { return a + b; }
}
new Calculator().add(2, 3); // logs: -> add(2, 3)
Class decorator
TS
function freeze<T extends new (...args: any[]) => any>(
target: T,
_ctx: ClassDecoratorContext,
) {
return class extends target {
constructor(...args: any[]) {
super(...args);
Object.freeze(this);
}
};
}
@freeze
class Config {
constructor(public name = 'default') {}
}
Field decorator
TS
function uppercase<This, Value extends string>(
_target: undefined,
_ctx: ClassFieldDecoratorContext<This, Value>,
) {
return function (this: This, value: Value): Value {
return value.toUpperCase() as Value;
};
}
class User {
@uppercase
name: string = 'ada'; // becomes 'ADA'
}
Decorator contexts
TS gives each decorator a typed context argument with the kind, name, access, and metadata:
ClassDecoratorContextClassMethodDecoratorContextClassFieldDecoratorContextClassGetterDecoratorContext/ClassSetterDecoratorContext/ClassAccessorDecoratorContext
Legacy decorators
Older code (often Angular, NestJS) uses experimental decorators. They look similar but have different signatures and require "experimentalDecorators": true in tsconfig. They live alongside stage-3 — not mutually compatible in the same file.
Tip: Don't use decorators for everyday business logic. They obscure control flow. Reserve them for cross-cutting concerns — logging, validation, dependency injection — and keep the implementations small.
Example
Example
// Stage-3 decorators land in TS 5+
// function log(_: unknown, ctx: ClassMethodDecoratorContext) {
// return function (this: any, ...args: any[]) {
// console.log('->', String(ctx.name), args);
// return (ctx as any).access.get.call(this).call(this, ...args);
// };
// }
console.log('Decorators wrap classes/methods with extra behaviour');
Try it Yourself »
Exercise
Decorator prefix character.
log
A single character.
Discussion
Loading…