TypeScript 데코레이터 | Decorators 완벽 가이드
이 글의 핵심
TypeScript 데코레이터에 대한 실전 가이드입니다. Decorators 완벽 가이드 등을 예제와 함께 상세히 설명합니다.
들어가며
데코레이터란?
데코레이터(Decorator)는 클래스·메서드·프로퍼티에 추가 정보를 붙이거나, 선언 시점에 공통 로직을 감싸 넣는 특별한 문법입니다. 횡단 관심(로깅, 권한 등)을 본문과 분리할 때 쓰입니다.
1. 설정
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
2. 클래스 데코레이터
기본 사용
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. 프로퍼티 데코레이터
기본 사용
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 모드)
실전 예제: 검증
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. 매개변수 데코레이터
기본 사용
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("[email protected]");
// 출력:
// [AUTH] login 호출됨
// 로그인: [email protected]
7. 실전 예제
예제 1: 권한 체크
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: 캐싱
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"); // 캐시에서 반환
정리
핵심 요약
- 클래스 데코레이터: 클래스 수정
- 메서드 데코레이터: 메서드 동작 수정
- 프로퍼티 데코레이터: 프로퍼티 제어
- 매개변수 데코레이터: 매개변수 메타데이터
- 데코레이터 팩토리: 매개변수 받기
다음 단계
- TypeScript 고급 패턴
- TypeScript 실전 프로젝트
- NestJS 데코레이터
관련 글
- TypeScript 시작하기 | 설치, 설정, 기본 문법