프론트엔드 스터디/Typescript

[TypeScript] 데코레이터

옹재 2021. 7. 20. 13:57
728x90
반응형

데코레이터

데코레이터는 클래스, 속성, 메서드, 접근 제어자, 매개변수 등에 사용할 수 있는 특별한 함수입니다. 선언된 데코레이터 함수를 사용할 때는 데코레이터 이름 앞에 @를 붙입니다.

// 데코레이터 함수
function Component(target:Function) {
  console.log(target);
  console.log(target.prototype);
}

// 데코레이터를 사용한 클래스 TabsComponent 정의
@Component
class TabsComponent {}

클래스, 속성, 메서드, 접근자, 매개변수에 따라 데코레이터 함수가 전달 받는 인자는 각각 다릅니다.

Decorator Factory

함수 사용 시, 사용자가 인자를 전달할 수 있는 것과 유사하게, 데코레이터 함수 또한 팩토리를 사용해 사용자로부터 인자를 전달 받도록 설정할 수 있습니다. 데코레이터 팩토리 함수는 데코레이터 함수를 감싸는 래퍼 함수입니다. 팩토리는 사용자로부터 전달 받은 인자를, 내부에서 반환되는 데코레이터 함수는 데코레이터로 사용됩니다

// 데코레이터 팩토리
function Component(value:string) {
  console.log(value);

  // 데코레이터 함수
  return function(target:Function) {
    console.log(target);
    console.log(target.prototype);
  }

}

// 데코레이터 팩토리를 사용하면 값을 전달할 수 있습니다.
@Component('tabs')
class TabsComponent {}

// TabsComponent 객체 생성
const tabs = new TabsComponent();

실행흐름

데코레이터의 실행 흐름은 다음 순으로 처리됩니다.

  1. 각 데코레이터 표현식은 위에서 아래 방향으로 평가됩니다.
  2. 결과는 아래에서 위로 함수를 호출합니다.
// Size 데코레이터 팩토리
function Size() {
  console.log('Size(): 평가됨');
  // Size 데코레이터
  return function(target:any, prop:string, desc:PropertyDescriptor){
    console.log('Size(): 실행됨')
  }
}

// Color 데코레이터 팩토리
function Color() {
  console.log('Color(): 평가됨');
  // Color 데코레이터
  return function(target:any, prop:string, desc:PropertyDescriptor){
    console.log('Color(): 실행됨')
  }
}

// Button 클래스 정의
class Button {

  // 메서드에 멀티 데코레이터 적용
  @Size()
  @Color()
  isPressed() {}

}

console에 출력되는 결과는 다음과 같습니다.

> Size(): 평가됨
> Color(): 평가됨
> Color(): 실행됨
> Color(): 실행됨

클래스 데코레이터

클래스에 설정하는 데코레이터를 말합니다. 클래스 데코레이터가 설정된 클래스 선언을 과찰, 수정 또는 대체하는데 사용할 수 있습니다. 클래스 데코레이터는 런타임에 함수로 호출 되며, 데코레이팅 되는 클래스가 유일한 인자로 전달되어 호출됩니다.

// Component 데코레이터
function Component(target:Function) {

  // 프로토타입 객체 참조
  let $ = target.prototype;

  // 프로토타입 객체 확장
  $.type = 'component';
  $.version = '0.0.1';

}

// Component 데코레이터 사용
@Component
class TabsComponent {};

// TabsComponent 객체 인스턴스 생성
const tabs = new TabsComponent();

// 데코레이터로 설정된 프로토타입 확장은 
// 타입 단언(Type Assertion) 필요
console.log((tabs as any).type); // 'component' 출력

메서드 데코레이터

메서드 데코레이터는 메서드 선언 앞에 사용됩니다. 데코레이터는 메서드 선언을 확인, 수정, 교체하는데 사용될 수 있습니다. 메서드 데코레이터 함수가 전달 받는 인자는 총 3가지로 다음과 같습니다.

  1. target: any
  2. prop: string
  3. descriptor: PropertyDescriptor
// Write 데코레이터 팩토리
function Write(able:boolean = true) {
  // Write 데코레이터
  return function(t:any,p:string,d:PropertyDescriptor) { 
    d.writable = able;
  }
}

// Button 클래스
class Button {
  // 생성자
  constructor(public el:HTMLButtonElement){}
  // Write 데코레이터 사용
  // false 전달 ⟹ 쓰기 불가
  @Write(false)
  disable(){ this.el.setAttribute('disabled', 'disabled'); }
}

// Button 객체 인스턴스 생성 및 변수 참조
const btn = new Button( <HTMLButtonElement>document.querySelector('.button') );

// [오류]
// 쓸 수 없는 메서드를 쓰려고 하였기에 쓸 수 없다고 오류 메시지를 출력합니다.
// Uncaught TypeError: 
// Cannot assign to read only property 'disable' of object '#<Button>'
btn.disable = function() { console.log(this); };

접근 제어자 데코레이터

접근 제어자 데코레이터는 접근 제어자 앞에 사용됩니다. 접근 제어자 데코레이터는 접근 제어자에 대한 선언 확인, 수정, 교체하는데 사용될 수 있습니다. 접근 제어자 데코레이터 함수가 전달 받는 인자는 총 3가지로 다음과 같습니다.

  1. target: any
  2. prop: string
  3. descriptor: PropertyDescriptor

속성 데코레이터

속성 데코레이터는 속성 선언 앞에 사용됩니다. 속성 데코레이터는 속성 선언을 확인, 수정, 교체하는데 사용될 수 있습니다. 앞에서 다룬 메서드, 접근 제어자와 달리 속성 데코레이터 함수는 전달 받는 인자가 총 2가지 입니다.

  1. target: any
  2. prop: string

매개변수 데코레이터

매개변수 데코레이터는 메서드 선언 앞에 사용됩니다. 매개변수 데코레이터는 클래스 생성자 함수 또는 메서드의 매개변수 에 사용될 수 있습니다. 매개변수 데코레이터 함수는 전달 받는 인자가 총 3가지 입니다.

  1. target: any
  2. prop: string
  3. parameter_index: number
// Log 매개변수 데코레이터
function Log(t:any, p:string, i:number) {
  console.log(t.name);
  console.log(`
    매개변수 순서: ${i}, 
    멤버 이름: ${p === undefined ? 'constructor' : p}
  `);
}

// Button 클래스
class Button {

  el:HTMLButtonElement;
  color:string;

  // 생성자 함수 내에 Log 데코레이터 사용
  constructor(
    @Log el:HTMLButtonElement, 
    @Log color:string = 'transparent'
  ) {
    this.el = el;
    this.color = color;
  }

  // 스태틱 메서드 내에 Log 데코레이터 사용
  static initialize(
    @Log els:NodeList, 
    @Log color:string = 'transparent'
  ){
    return [].slice.call(els).map(el => new Button(el, color));
  }

}

// Button.initialize() 스태틱 메서드 사용
const btns = Button.initialize( document.querySelectorAll('.button'), '#900' );
728x90
반응형