본문으로 건너뛰기 TypeScript Decorators — Complete Guide

TypeScript Decorators — Complete Guide

TypeScript Decorators — Complete Guide

이 글의 핵심

TypeScript decorators: experimentalDecorators, class/method/property decorators, decorator factories, logging, validation, authorization, and caching patterns.

Introduction

What are decorators?

A decorator is a special declaration that adds metadata to classes, methods, properties, and more—or wraps their behavior.

1. Configuration

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

2. Class decorators

Basic usage

function sealed(constructor: Function) {
    Object.seal(constructor);
    Object.seal(constructor.prototype);
}
@sealed
class User {
    name: string;
    
    constructor(name: string) {
        this.name = name;
    }
}

Example: logging

function logger<T extends { new(...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
        constructor(...args: any[]) {
            super(...args);
            console.log(`${constructor.name} 인스턴스 생성됨`);
        }
    };
}
@logger
class User {
    constructor(public name: string) {}
}
const user = new User("홍길동");
// Output: User 인스턴스 생성됨

3. Method decorators

Basic usage

function log(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
) {
    const originalMethod = descriptor.value;
    
    descriptor.value = function(...args: any[]) {
        console.log(`${propertyKey} 호출됨:`, args);
        const result = originalMethod.apply(this, args);
        console.log(`${propertyKey} 결과:`, result);
        return result;
    };
    
    return descriptor;
}
class Calculator {
    @log
    add(a: number, b: number): number {
        return a + b;
    }
}
const calc = new Calculator();
calc.add(10, 20);
// Output:
// add 호출됨: [10, 20]
// add 결과: 30

Example: timing

function measure(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
) {
    const originalMethod = descriptor.value;
    
    descriptor.value = async function(...args: any[]) {
        const start = performance.now();
        const result = await originalMethod.apply(this, args);
        const end = performance.now();
        console.log(`${propertyKey} 실행 시간: ${(end - start).toFixed(2)}ms`);
        return result;
    };
    
    return descriptor;
}
class DataService {
    @measure
    async fetchData() {
        await new Promise(resolve => setTimeout(resolve, 1000));
        return { data: "결과" };
    }
}
const service = new DataService();
await service.fetchData();
// Output: fetchData 실행 시간: 1001.23ms

4. Property decorators

Basic usage

function readonly(target: any, propertyKey: string) {
    Object.defineProperty(target, propertyKey, {
        writable: false
    });
}
class User {
    @readonly
    id: string = "U001";
    
    name: string = "홍길동";
}
const user = new User();
console.log(user.id);  // U001
// user.id = "U002";   // Error (strict mode)

Example: validation

function validate(validationFn: (value: any) => boolean) {
    return function(target: any, propertyKey: string) {
        let value: any;
        
        Object.defineProperty(target, propertyKey, {
            get() {
                return value;
            },
            set(newValue: any) {
                if (!validationFn(newValue)) {
                    throw new Error(`${propertyKey} 검증 실패`);
                }
                value = newValue;
            }
        });
    };
}
class User {
    @validate((value) => value.length >= 2)
    name!: string;
    
    @validate((value) => value >= 0 && value <= 150)
    age!: number;
}
const user = new User();
user.name = "홍길동";  // ✅
user.age = 25;        // ✅
// user.name = "a";   // ❌ Error
// user.age = 200;    // ❌ Error

5. Parameter decorators

Basic usage

function required(
    target: any,
    propertyKey: string,
    parameterIndex: number
) {
    console.log(`${propertyKey}의 ${parameterIndex}번째 매개변수는 필수입니다`);
}
class User {
    greet(@required name: string) {
        console.log(`안녕하세요, ${name}님!`);
    }
}

6. Decorator factories

Concept

Factories return a decorator so you can pass configuration.

function log(prefix: string) {
    return function(
        target: any,
        propertyKey: string,
        descriptor: PropertyDescriptor
    ) {
        const originalMethod = descriptor.value;
        
        descriptor.value = function(...args: any[]) {
            console.log(`[${prefix}] ${propertyKey} 호출됨`);
            return originalMethod.apply(this, args);
        };
        
        return descriptor;
    };
}
class UserService {
    @log("USER")
    createUser(name: string) {
        console.log(`사용자 생성: ${name}`);
    }
    
    @log("AUTH")
    login(email: string) {
        console.log(`로그인: ${email}`);
    }
}
const service = new UserService();
service.createUser("홍길동");
// Output:
// [USER] createUser 호출됨
// 사용자 생성: 홍길동
service.login("hong@test.com");
// Output:
// [AUTH] login 호출됨
// 로그인: hong@test.com

7. Practical examples

Example 1: Authorization

function authorize(roles: string[]) {
    return function(
        target: any,
        propertyKey: string,
        descriptor: PropertyDescriptor
    ) {
        const originalMethod = descriptor.value;
        
        descriptor.value = function(...args: any[]) {
            const userRole = getCurrentUserRole();  // e.g. from session
            
            if (!roles.includes(userRole)) {
                throw new Error("Forbidden");
            }
            
            return originalMethod.apply(this, args);
        };
        
        return descriptor;
    };
}
function getCurrentUserRole(): string {
    return "admin";  // In real apps, read from session/JWT
}
class AdminService {
    @authorize([admin])
    deleteUser(id: string) {
        console.log(`사용자 삭제: ${id}`);
    }
    
    @authorize(["admin", "moderator"])
    banUser(id: string) {
        console.log(`사용자 차단: ${id}`);
    }
}
const service = new AdminService();
service.deleteUser("U001");  // ✅ succeeds as admin

Example 2: Caching

function cache(ttl: number = 60000) {
    const cacheStore = new Map<string, { value: any; expiry: number }>();
    
    return function(
        target: any,
        propertyKey: string,
        descriptor: PropertyDescriptor
    ) {
        const originalMethod = descriptor.value;
        
        descriptor.value = async function(...args: any[]) {
            const key = `${propertyKey}_${JSON.stringify(args)}`;
            const cached = cacheStore.get(key);
            
            if (cached && Date.now() < cached.expiry) {
                console.log("캐시에서 반환");
                return cached.value;
            }
            
            console.log("새로 계산");
            const result = await originalMethod.apply(this, args);
            cacheStore.set(key, { value: result, expiry: Date.now() + ttl });
            return result;
        };
        
        return descriptor;
    };
}
class DataService {
    @cache(5000)  // 5 second TTL
    async fetchUser(id: string) {
        await new Promise(resolve => setTimeout(resolve, 1000));
        return { id, name: "홍길동" };
    }
}
const service = new DataService();
await service.fetchUser("U001");  // 새로 계산
await service.fetchUser("U001");  // 캐시에서 반환

Summary

Takeaways

  1. Class decorators: wrap or modify the constructor
  2. Method decorators: wrap method calls
  3. Property decorators: attach behavior to properties
  4. Parameter decorators: record parameter metadata (often with reflection libraries)
  5. Decorator factories: return configured decorators

Next steps



Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. TypeScript decorators: experimentalDecorators, class/method/property decorators, decorator factories, logging, validatio…

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers TypeScript, Decorators, Metadata, Metaprogramming, Experimental.