[2026] TypeScript 데코레이터 | Decorators 완벽 가이드

[2026] TypeScript 데코레이터 | Decorators 완벽 가이드

이 글의 핵심

TypeScript 데코레이터: Decorators 클래스 데코레이터·메서드 데코레이터.

들어가며

데코레이터란?

데코레이터(Decorator)는 클래스·메서드·프로퍼티에 추가 정보를 붙이거나, 선언 시점에 공통 로직을 감싸 넣는 특별한 문법입니다. 횡단 관심(로깅, 권한 등)을 본문과 분리할 때 쓰입니다.

JavaScript에서 TypeScript로의 전환

“런타임 에러는 프로덕션에서 터진다”는 말이 있습니다. JavaScript로 개발하던 시절, 이 말을 뼈저리게 체감했습니다. 테스트는 다 통과했는데 배포 후 사용자가 이상한 데이터를 입력하면 앱이 터지는 거죠. 특히 팀 프로젝트에서 다른 사람이 작성한 함수의 반환 타입을 잘못 이해해서 버그가 발생하는 일이 잦았습니다. TypeScript를 도입한 후 가장 먼저 느낀 건 안심이었습니다. IDE가 자동완성을 정확히 해주고, 타입이 맞지 않으면 빨간 줄로 미리 알려줍니다. 리팩토링할 때도 “이 함수를 바꾸면 어디가 깨질까?” 걱정할 필요가 없어졌죠. 물론 처음엔 타입 정의 작성이 번거로웠지만, 몇 달 후 그 타입 정의 덕분에 버그를 미리 잡는 경험을 하고 나니 돌아갈 수 없었습니다.

1. 설정

tsconfig.json

아래 코드는 json를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

2. 클래스 데코레이터

기본 사용

아래 코드는 typescript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

실전 예제: 로깅

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("홍길동");
// 출력: User 인스턴스 생성됨

3. 메서드 데코레이터

기본 사용

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);
// 출력:
// add 호출됨: [10, 20]
// add 결과: 30

실전 예제: 성능 측정

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();
// 출력: fetchData 실행 시간: 1001.23ms

4. 프로퍼티 데코레이터

기본 사용

다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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";   // 에러 (strict 모드)

실전 예제: 검증

다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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";   // ❌ 에러
// user.age = 200;    // ❌ 에러

5. 매개변수 데코레이터

기본 사용

아래 코드는 typescript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

6. 데코레이터 팩토리

개념

매개변수를 받는 데코레이터를 만듭니다.

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("홍길동");
// 출력:
// [USER] createUser 호출됨
// 사용자 생성: 홍길동
service.login("hong@test.com");
// 출력:
// [AUTH] login 호출됨
// 로그인: hong@test.com

7. 실전 예제

예제 1: 권한 체크

다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

function authorize(roles: string[]) {
    return function(
        target: any,
        propertyKey: string,
        descriptor: PropertyDescriptor
    ) {
        const originalMethod = descriptor.value;
        
        descriptor.value = function(...args: any[]) {
            const userRole = getCurrentUserRole();  // 현재 사용자 역할
            
            if (!roles.includes(userRole)) {
                throw new Error("권한이 없습니다");
            }
            
            return originalMethod.apply(this, args);
        };
        
        return descriptor;
    };
}
function getCurrentUserRole(): string {
    return "admin";  // 실제로는 세션에서 가져옴
}
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");  // ✅ admin이므로 성공

예제 2: 캐싱

다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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초 캐싱
    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");  // 캐시에서 반환

정리

핵심 요약

  1. 클래스 데코레이터: 클래스 수정
  2. 메서드 데코레이터: 메서드 동작 수정
  3. 프로퍼티 데코레이터: 프로퍼티 제어
  4. 매개변수 데코레이터: 매개변수 메타데이터
  5. 데코레이터 팩토리: 매개변수 받기

다음 단계


관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3