: 여러 컴포넌트 간에 공통으로 사용하고 있는 로직, 기능들을 재사용하는 방법
: 동작과 프로퍼티를 클래스로 혼합할 수 있게 해주는 패턴
: 동작을 캡슐화할 뿐만 아니라 동작을 재사용할 수 있도록 도와줌
// 모든 생성자
type ClassConstuctor = new (...args: any[]) => {}
// C는 최소 클래스 생성자
function withEZDebug<C extends ClassConstructor>(Class: C) {
// C를 상속하는 익명 클래스 생성자 반환
return class extends Class {
// constructor에 아무런 로직이 없으면 생략 가능
constructor(...args: any[]) {
super(...args)
}
debug() {
let Name = this.constructor.name
let value = this.getDebugValue()
return `${Name} (${JSON.stringify(value)})`
}
}
// 제네릭 타입 매개변수 추가
type ClassConstuctor<T> = new (...args: any[]) => T
function withEZDebug<C extends ClassConstructor<{getDebugValue(): object}>>(Class: C) {
return class extends Class {
debug() {
let Name = this.constructor.name
let value = this.getDebugValue()
return `${Name} (${JSON.stringify(value)})`
}
}
class HardToDebugUser {
constructor(private id: number, private firstName: string, private lastName: string) {
}
getDebugValue() {
return {
id: this.id,
name: this.firstName + ' ' + this.lastName
}
}
}
let User = withEZDebug(HardToDebugUser)
let user = new User(3, 'Emma', 'Gluzman')
user.debug() // 결과: HardToDebugUser({"id": 2, "name": "Emma Gluzman"})
: 타입스크립트의 실험적 기능
: 장식하는 대상의 함수를 호출하는 기능 제공
: 아직 실험 단계의 이능이므로 TSC 플래그를 따로 설정해야함(experfimentalDecorators: true)
: 타입스크립트가 기본을 제공하는 데코레이터는 없음
=> 모든 데코레이터는 직접 구현하거나 NPM으로 설치해야함
데코레이터 타입 각각에 대해 주어진 이름 범위에 존재하는 함수와 해당 데코레이터 타입에 요구되는 시그니처를 필요로 함
데코레이터 대상 | 기대하는 타입 시그니처 |
---|---|
클래스 | (Constructor: {new(...any[]) => any}) => any |
메서드 | Method (classPrototype: {}, methodName: string, descriptor: PropertyDescriptor) => any |
정적 메서드 | (Constructor: {new(...any[]) => any}, methodName: string, descriptor: PropertyDescriptor) => any |
메서드 매개변수 | (classPrototype: {}, paramName: string, index: number) => void |
정적 메서드 매개변수 | (Constructor: {new(...any[]) => any}, paramName: string, index: number) => void |
프로퍼티 | (classPrototype: {}, propertyName: string) => any |
정적 프로퍼티 | (Constructor: {new(...any[]) => any}, propertyName: string) => any |
프로퍼티 게터/세터 | (classPrototype: {}, propertyName: string, descriptor: PropertyDescriptor) => any |
정적 프로퍼티 게터/세터 | (Constructor: {new(...any[]) => any}, propertyName: string, descriptor: PropertyDescriptor) => any |
데코레이터 구현 예제
type ClassConstuctor<T> = new (...args: any[]) => T
function serializable<T extends ClassConstructor<{getValue(): Payload}>>(Constructor: T) {
// 주어진 클래스를 상속한 다음 .serialize 메서드를 추가해서 클래스를 장식
return class extends Constructor {
serialize() {
return this.getValue().toString()
}
}
// 클래스 데코레이터
@serializable
class APIPayload {
getValue(): Payload {
...
}
}
// 데코레이터의 메서드 실행
let payload = new APIPayload
let serialized = payload.serialize() // Error: 'serialize' 프로퍼ㅣ는 'APIPayload' 타입에 존재하지 않음
// 데코레이터 대신 일반 함수로 실행
let DecoratedAPIPayload = serializable(APIPayload)
let paylaod = new DecoratedAPIPayload
payload.serialize()
데코레이터의 메서드 실행 시 오류 발생 이유
: 타입스크립트는 데코레이터가 장식하는 대성의 형태를 바꾸지 않는다고 가정
= 메서드나 프로퍼티를 추가하거나 삭제하지 않았다고 가정
=> 데코레이터 대신 일반 함수 사용 권장
: 클래스나 메서드를 확장하거나 오버라이드할 수 없게 만드는 기능
: 타입스크립트는 클래스나 메서드에 final 키워드를 지원하지 않지만 '비공개 생성자(private constructor)'로 fianl 클래스를 흉내낼 수 있음
class MessageQueue {
private constructor(private messages: string[]) { }
// 비공개 생성자 사용으로 클래스를 인스턴스화 하지 못하는 문제 해결
static create(messages: string[]) {
return new MessageQueue(messages)
}
}
class BadQueue extends MessageQueue { } // Error: 'MessageQueue' 클래수를 확장할 수 없음
// 클래스 생성자가 private으로 설정됨
new MessageQueue([]) // Error: 'MessageQueue' 클래스 생성자가
// private이므로 클래스 내부 선언에서만 접근 가능
MessageQueue.create([]) // MessageQueue
: 어떤 객체를 만들지를 전적으로 팩토리에 위임
type Shoe = {
purpose: string
}
class BalletFalt implements Shoe {
purpose = 'dancing'
}
class Boot implements Shoe {
purpose = 'woodcutting'
}
class Sneaker implements Shoe {
purpose = 'walking'
}
let Shoe = {
// type을 유니온 타입으로 지정하여 유효하지 않은 type을 전달하지 못하도록 방지
create(type: 'balletFlat' | 'boot' | 'sneaker'): Shoe {
// switch문을 이용해 누락된 Shoe 타입이 없는지 타입스크립트가 확인 가능
switch (type) {
case 'balletFlat': return new BalletFlat
case 'boot': return new Boot
case 'sneaker': return new Sneaker
}
}
}
Shoe.create('boot') // Shoe
// create의 타입 시그니처를 수정하여 반환값을 드러내도록 하면 추상화 규칙을 깨버리게 됨
: 객체의 생성과 객체 구현 방식을 분리
class RequestBuilder {
private url: string | null = null
private data: object | null = null
private method: 'get' | 'post' | null = null
setURL(url: string): this {
this.url = url
return this
}
setData(data: object): this {
this.data = data
return this
}
send() {
...
}
}
// 호출 순서가 바뀐다면 런타임 예외 발생가능 (ex. send를 먼저 호출)
// 설계 개선이 필요
new ReuqestBuilder()
.setURL('/users')
.setMethod('get')
.setData({ firstName: 'Anna' })
.send()