TypeScript - Decorators

이소라·2022년 7월 30일
0

TypeScript

목록 보기
10/28

Decorators

  • decorator로 지정할 함수 이름 앞에 @ 기호를 붙여줌
  • decorator는 class가 정의만 되도 실행됨 (class가 인스턴스화되기 전에 실행)
  • decorator에는 인자를 가지고 있고, 이 인자는 decorator를 사용하는 위치에 따라 달라짐
    • 인자를 사용하지 않을 경우, -를 써서 사용하지 않을 것을 명시해줌
function Logger(constructor: Function) {
  console.log('Logging...');
  console.log(constructor);
}

@Logger
class Person {
  name = 'Max';
  
  constructor() {
    console.log('Creating Person object...');
  }
}

const pers = new Person();

Decorator를 사용하기 위한 tsconfig 설정

  • target 속성이 es6 이상이어야 함
  • experimentalDecorators 속성이 true이어야 함

Decorator Factory

  • decorator 함수를 반환하는 함수
  • 장점 : 함수의 인자로 받은 값을 decorator에 전달해줄 수 있음
function Logger(logString: string) {
  return function(constructor: Function) {
    console.log(logString);
  	console.log(constructor);
  }
}

@Logger('Login - Person')
class Person {
  name = 'Max';
  
  constructor() {
    console.log('Creating Person object...');
  }
}

const pers = new Person();

Decorator Factory Example

  • decorator를 사용하여 DOM을 조작할 수 있음
function WithTemplate(template: string, hookId: string) {
  return function(_: Function) {
    const hookEl = document.getElementById(hookId);
    if (hookEl) {
      hookEl.innerHTML = template;
    }
  }
}

@WithTemplate('<h1>My Person Object</h1>', 'app')
class Person {
  name = 'Max';
  
  constructor() {
    console.log('Creating Person object...');
  }
}

const pers = new Person();
  • class의 constrcutor에 접근할 수 있음
function WithTemplate(template: string, hookId: string) {
  return function(constructor: any) {
    const hookEl = document.getElementById(hookId);
    const p = new constructor();
    if (hookEl) {
      hookEl.innerHTML = template;
      hookEl.querySelector('h1')!.textContent = p.name;
    }
  }
}

@WithTemplate('<h1>My Person Object</h1>', 'app')
class Person {
  name = 'Max';
  
  constructor() {
    console.log('Creating Person object...');
  }
}

const pers = new Person();

여러 개의 Decorator Factory 추가하기

  • 여러 개의 decorator factory를 추가하는 경우, factory 함수들이 먼저 실행되고(선언한 순서대로) 그 이후에 decorator들이 실행됨(아래에서 위순으로)
function Logger(logString: string) {
  console.log('Logger Factory'); // 실행 순서 1
  return function(constructor: Function) {
    console.log(logString); // 실행 순서 4
  	console.log(constructor);
  }
}

function WithTemplate(template: string, hookId: string) {
  console.log('WithTemplate Factory'); // 실행 순서 2
  return function(constructor: any) {
    const hookEl = document.getElementById(hookId); // 실행 순서 3
    const person = new constructor();
    if (hookEl) {
      hookEl.innerHTML = template;
      hookEl.querySelector('h1')!.textContent = p.name;
    }
  }
}

@Logger('Login - Person')
@WithTemplate('<h1>My Person Object</h1>', 'app')
class Person {
  name = 'Max';
  
  constructor() {
    console.log('Creating Person object...');
  }
}

const pers = new Person();

Property Decorator

  • property decorator는 클래스의 일부분이므로 클래스를 정의할 때 실행됨
function Log(target: any, propertyName: string | Symbol) {
  console.log('Property decorator!');
  console.log(target, propertyName); // Class Object의 프로토타입, title
}

class Product {
  @Log
  title: string;
  private _price: number;
  
  set price(val: number) {
    if (val > 0) {
      this._price = val;
    }
  }
  
  constructor(t: string, p: number) {
    this.title = t;
    this._price = p;
  }
  
  getPriceWithTax(tax: number) {
    return this._price * (1 + tax);
  }
}

Accessor Decorator & Parameter Decorator

Accessor Decorator

  • acccess decorator는 writable이 true임
function Log(target: any, propertyName: string | Symbol) {
  console.log('Property decorator!');
  console.log(target, propertyName);
}

function Log2(target: any, name: string, descriptor: PropertyDecorator) {
  console.log('Access decorator!');
  console.log(target);
  console.log(name); // price
  console.log(descriptor);
}

class Product {
  @Log
  title: string;
  private _price: number;
  
  @Log2
  set price(val: number) {
    if (val > 0) {
      this._price = val;
    }
  }
  
  constructor(t: string, p: number) {
    this.title = t;
    this._price = p;
  }
  
  getPriceWithTax(tax: number) {
    return this._price * (1 + tax);
  }
}

Paramter Decorator

function Log(target: any, propertyName: string | Symbol) {
  console.log('Property decorator!');
  console.log(target, propertyName);
}

function Log2(target: any, name: string, descriptor: PropertyDecorator) {
  console.log('Access decorator!');
  console.log(target);
  console.log(name);
  console.log(descriptor);
}
function Log3(target: any, name: string, descriptor: PropertyDecorator) {
  console.log('Method decorator!');
  console.log(target);
  console.log(name);
  console.log(descriptor);
}

function Log4(target: any, name: string | Symbol, position: number) {
  console.log('Paramter decorator!');
  console.log(target);
  console.log(name); // getPriceWithTax (parameter를 가지는 메소드의 이름)
  console.log(position); // 0 (0번째 paramter)
}

class Product {
  @Log
  title: string;
  private _price: number;
  
  @Log2
  set price(val: number) {
    if (val > 0) {
      this._price = val;
    }
  }
  
  constructor(t: string, p: number) {
    this.title = t;
    this._price = p;
  }
  
  @Log3
  getPriceWithTax(@Log4 tax: number) {
    return this._price * (1 + tax);
  }
}

0개의 댓글