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
- 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
- TypeScript project: REST API
- NestJS decorators (controllers, providers, guards)
Related posts
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.
Related Articles (Internal Links)
Other articles related to this topic.
- TypeScript Utility Types | Partial· Pick
- Advanced TypeScript | Conditional Types· Template Literals
- TypeScript REST API Project | Express· Layered Architecture
Keywords Covered in This Article (Related Search Terms)
This article covers TypeScript, Decorators, Metadata, Metaprogramming, Experimental.