Open
Description
TypeScript 5
Decorators

기존의 알맹이(Object)에 껍데기(Decorator)를 씌운다.
- Decorator 패턴 자체는 새로운 개념이 아닌 이미 널리 사용되던 디자인 패턴 (자세히 알아보기)
- ECMAScript - Decorator (Stage 3)
- 곧 표준으로 자리잡을 예정
Before
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
greet() {
console.log(`Hello, my name is ${this.name}.`);
}
}
const p = new Person("Ray");
p.greet();
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
greet() {
// 로깅 로직 추가
console.log("LOG: Entering method.");
console.log(`Hello, my name is ${this.name}.`);
console.log("LOG: Exiting method.")
}
}
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
greet() {
// 로깅 로직 추가
console.log("LOG: Entering method.");
console.log(`Hello, my name is ${this.name}.`);
console.log("LOG: Exiting method.")
}
hello() {
// 로깅 로직 추가
console.log("LOG: Entering method.");
console.log(`Hello, my name is ${this.name}.`);
console.log("LOG: Exiting method.")
}
}
Use Decorator
function loggedMethod(originalMethod: any, _context: any) {
function replacementMethod(this: any, ...args: any[]) {
console.log("LOG: Entering method.")
const result = originalMethod.call(this, ...args);
console.log("LOG: Exiting method.")
return result;
}
return replacementMethod;
}
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
// great이 loggedMethod의 인수로 전달된다.
@loggedMethod
greet() {
console.log(`Hello, my name is ${this.name}.`);
}
@loggedMethod
hello() {
console.log(`Hello, my name is ${this.name}.`);
}
}
const p = new Person("Ray");
p.greet();
// Output:
//
// LOG: Entering method.
// Hello, my name is Ray.
// LOG: Exiting method.
주의할 점
class 내부
에서만 사용할 수 있다.runtime에 호출
된다.
실제 활용 사례
에러 클래스 생성 및 로깅 로직은 decorator로 재사용 가능하도록 활용
import { captureException } from 'sentry/nextjs';
function logSentry(targetClass: any) {
// the new constructor behaviour
const wrapper : any = function (...args) {
// setnry에 로깅
sentry.captureException(error, { level: 'error', extra: { args } });
return new original(...args);
}
wrapper.prototype = targetClass.prototype;
return wrapper;
}
@logSentry
export class ImageError extends Error {
constructor(imageUrl: string, pathname: string, level?: Sentry.SeverityLevel) {
super(`${imageUrl}을 로드하는 중 에러가 발생했습니다 (page url: ${pathname})`);
this.name = 'ImageError';
}
}
@logSentry
export class NetworkError extends Error {
public statusCode: number;
constructor(statusCode: number, responseData?: string | Record<string, any>) {
const message =
typeof responseData === 'string'
? `${statusCode}: ${responseData}`
: `${statusCode}: ${JSON.stringify(responseData)}`;
super(message);
this.name = 'NetworkError';
}
}
const Type Parameters
type HasNames = { names: readonly string[] };
function getNamesExactly<T extends HasNames>(arg: T): T["names"] {
return arg.names;
}
// 우리가 원하는 타입:
// readonly ["Alice", "Bob", "Eve"]
// 하지만 실제 추론된 타입:
// string[]
const names1 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]});
// 하지만 우리는 정확하게 아래처럼 되길 원합니다. 하지만 `as const`를 항상 붙이는 것은 잊기 쉽습니다.
// readonly ["Alice", "Bob", "Eve"]
const names2 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]} as const);
//////////////// TypeScript 5, const Type Parameters 이후 //////////////////////////
type TS5_HasNames = { names: readonly string[] };
function ts5_getNamesExactly<const T extends HasNames>(arg: T): T["names"] {
// ^^^^^
return arg.names;
}
// 추론된 타입: readonly ["Alice", "Bob", "Eve"]
// TypeScript 4.
'as const' 가 불필요
const names = ts5_getNamesExactly({ names: ["Alice", "Bob", "Eve"] });
Metadata
Metadata
Assignees
Labels
No labels