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("[email protected]");
// Output:
// [AUTH] login 호출됨
// 로그인: [email protected]
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
- Class decorators: wrap or modify the constructor
- Method decorators: wrap method calls
- Property decorators: attach behavior to properties
- Parameter decorators: record parameter metadata (often with reflection libraries)
- Decorator factories: return configured decorators
Next steps
- [Advanced TypeScript patterns](/en/blog/typescript-series-07-advanced/
- [TypeScript project: REST API](/en/blog/typescript-series-08-project/
- NestJS decorators (controllers, providers, guards)
Related posts
- [TypeScript getting started | install, config, syntax](/en/blog/typescript-series-01-intro/
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. TypeScript decorators: experimentalDecorators, class/method/property decorators, decorator factories, logging, validatio… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- [TypeScript Utility Types | Partial· Pick](/en/blog/typescript-series-05-utility-types/
- [Advanced TypeScript | Conditional Types· Template Literals](/en/blog/typescript-series-07-advanced/
- [TypeScript REST API Project | Express· Layered Architecture](/en/blog/typescript-series-08-project/
이 글에서 다루는 키워드 (관련 검색어)
TypeScript, Decorators, Metadata, Metaprogramming, Experimental 등으로 검색하시면 이 글이 도움이 됩니다.