← TypeScript EnglishChapter 11 of 12

Decorators

## Learning Objectives - Understand decorator concept - Use class decorators - Apply method decorators - Work with property decorators - Configure TypeScript for decorators ## Overview Decorators are an experimental feature (stage 2) that allow you to modify classes and their members. They provide a way to add metadata or change behavior. ## Enabling Decorators ### tsconfig.json ```json { "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } } ``` ## Class Decorators ### Basic Syntax ```typescript function sealed(constructor: Function) { Object.seal(constructor); Object.seal(constructor.prototype); } @sealed class Person { name: string; constructor(name: string) { this.name = name; } } ``` ### Decorator Factories ```typescript function color(value: string) { return function (constructor: Function) { constructor.prototype.color = value; }; } @color("blue") class Car { brand: string; } console.log((new Car() as any).color); // "blue" ``` ## Method Decorators ### Accessor Decorators ```typescript function readonly( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { descriptor.writable = false; } class Person { @readonly name(): string { return "Alice"; } } let p = new Person(); // p.name = () => "Bob"; // Error: Cannot assign ``` ### Method Decorator ```typescript function log( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { const original = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`Calling ${propertyKey} with`, args); return original.apply(this, args); }; } class Calculator { @log add(a: number, b: number): number { return a + b; } } let calc = new Calculator(); calc.add(2, 3); // Calling add with [2, 3] // 5 ``` ## Property Decorators ```typescript function defaultValue(value: any) { return function ( target: any, propertyKey: string ) { target[propertyKey] = value; }; } class Config { @defaultValue("localhost") host: string; } let config = new Config(); console.log(config.host); // "localhost" ``` ## Parameter Decorators ```typescript function required( target: any, propertyKey: string | symbol, parameterIndex: number ) { // Mark parameter as required } function validate( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { const original = descriptor.value; descriptor.value = function (...args: any[]) { // Validate required parameters return original.apply(this, args); }; } class UserService { create( @required name: string, @required age: number ): User { return { name, age }; } } ``` ## Decorator Composition ### Multiple Decorators ```typescript function first() { console.log("first(): evaluated"); return function ( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { console.log("first(): called"); }; } function second() { console.log("second(): evaluated"); return function ( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { console.log("second(): called"); }; } class Example { @first() @second() method() {} } // Output: // first(): evaluated // second(): evaluated // second(): called // first(): called ``` ## Class Method Decorators ### Logging Decorator ```typescript function logExecution(label: string) { return function ( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { const original = descriptor.value; descriptor.value = function (...args: any[]) { const start = Date.now(); const result = original.apply(this, args); const duration = Date.now() - start; console.log(`${label} took ${duration}ms`); return result; }; }; } class UserService { @logExecution("getUser") getUser(id: number) { // expensive operation return { id, name: "Alice" }; } } ``` ## Property Descriptor ### Available Properties ```typescript { value?: any, writable?: boolean, enumerable?: boolean, configurable?: boolean, get?: () => any, set?: (v: any) => void } ``` ## Reflect Metadata ### Installation ```bash npm install reflect-metadata ``` ### Example ```typescript import "reflect-metadata"; function required( target: Object, propertyKey: string, parameterIndex: number ) { const requiredParams: number[] = Reflect.getMetadata("required", target, propertyKey) || []; requiredParams.push(parameterIndex); Reflect.defineMetadata( "required", requiredParams, target, propertyKey ); } class Person { greet(@required name: string): string { return `Hello, ${name}`; } } ``` ## Common Use Cases ### Singleton Pattern ```typescript function singleton( constructor: T ) { let instance: T; return class extends constructor { constructor(...args: any[]) { if (instance) { return instance; } instance = this as T; super(...args); } }; } @singleton class Database { constructor() { console.log("Database connected"); } } let db1 = new Database(); let db2 = new Database(); console.log(db1 === db2); // true ``` ### Auto-bind ```typescript function autoBind( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { const original = descriptor.value; return { configurable: true, enumerable: false, get() { return original.bind(this); } }; } class Component { @autoBind render() { console.log(this); } } ``` ## Limitations ### Experimental - Not part of official ECMAScript yet - API may change - Use with caution in production ### Order of Execution - Decorators evaluated top to bottom - Decorators called bottom to top - Be aware when combining multiple decorators ## Summary - Decorators are experimental in TypeScript - Enable with `experimentalDecorators` - Class decorators receive constructor - Method/accessor decorators receive descriptor - Property decorators receive prototype - Parameter decorators receive index - Decorator factories return decorator functions - Multiple decorators compose with evaluation/call order

Comments

Comments powered by Giscus

To enable comments, add your Giscus embed code here.

Learn more about Giscus →