Programming/JavaScript & TypeScript

JavaScript의 Decorator

Bonita SY 2020. 9. 28. 22:29
728x90
반응형

[ Decorator ]

- 2020년 9월 meeting에서 TC39에 제시될 새로운 제안

- 가장 단순하게는 코드의 한 부분을 다른 코드로 감싸는 방법 (= 장식)

- 정의 중에 클래스 요소, 기타 JavaScript 구문 양식에서 호출되는 함수, 잠재적으로 decorator가 반환한 새 값으로 래핑(또는 대체)됨

 

※ 사실 이미 다른 방식으로 decorator의 느낌처럼 JavaScript에서 사용 가능했음..(아래 처럼)

function loggingDecorator(wrapped) {
  return function() {
    console.log('Starting');
    const result = wrapped.apply(this, arguments);
    console.log('Finished');
    return result;
  }
}

const wrapped = loggingDecorator(doSomething);

 

< JavaScript에서 Decorator 사용 방법 >

 @ + 데코레이팅할 코드 

 

- 원하는만큼 사용 가능

- 선언한 순서대로 적용

- class 선언, method, accessor, property, parameter에 모두 선언 가능

 

예제) 

@log()
@immutable()
class Example {
  @time('demo')
  doSomething() {
    //
  }
}

예제 설명)

- 클래스를 정의하고 3개의 decorator를 적용

- 2개는 클래스 자체에, 1개는 클래스 속성에 적용

- @log : 클래스에 대한 모든 access를 기록

- @immutable : 클래스를 변경 불가능하게 만듦 / 새 인스턴스에서 Object.freeze를 호출 가능

- @time : 메서드가 실행되는 데 걸리는 시간을 기록하고 고유태그를 사용하여 로그아웃

 

 

- 현재 decorator를 사용하려면, 트랜스파일러 지원이 필요

- babel을 사용하는 경우 transform-decorators-legacy 플러그인을 사용하여 간단히 활성화 가능

--> 플러그인에서 사용되는 레거시는 babel5방식을 지원(표준이 아님)

 

< Decorator를 사용하는 이유 >

- JavaScript에서 함수 구성이 이미 가능하지만 동일한 기술을 다른 코드(클래스나 클래스 속성)에 적용하는 것이 훨씬 어렵거나 불가능 => Decorator 제안은 이런 문제를 해결하는데 사용할 수 있음

- 작성중인 코드의 실제 의도를 더 명확하게 파악 가능

- 코드가 간결해지고 명확해짐

 

< Decorator 유형 >

- 현재 지원되는 decorator 유형은 클래스 및 클래스 멤버(속성, 메서드, getter, setter)

- decorator는 실제로 다른 함수를 반환하고, 데코레이팅되는 항목의 적절한 세부사항과 함게 호출되는 함수일 뿐

- decorator 함수는 프로그램이 처음 실행될 때 한번 evaluate되고, 데코레이팅된 코드는 반환값으로 대체됨

1. Class member decorators

- Property decorator는 속성, 메서드, getter, setter 등 클래스의 단일 멤버에 적용

- Property decorator 함수는 세가지 메개변수로 호출됨

- (1) target : 멤버가 있는 클래스

- (2) name : 클래스의 멤버 이름

- (3) descriptor : Object.defineProperty에 전달되는 객체

 

ex1)

@readonly 사용법

function readonly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

class Example {
  a() {}
  @readonly
  b() {}
}

const e = new Example();
e.a = 1;
e.b = 2;
// TypeError: Cannot assign to read only property 'b' of object '#<Example>'

ex2)

@log 사용법

function log(target, name, descriptor) {
  const original = descriptor.value;
  if (typeof original === 'function') {
    descriptor.value = function(...args) {
      console.log(`Arguments: ${args}`);
      try {
        const result = original.apply(this, args);
        console.log(`Result: ${result}`);
        return result;
      } catch (e) {
        console.log(`Error: ${e}`);
        throw e;
      }
    }
  }
  return descriptor;
}

class Example {
  @log
  sum(a, b) {
    return a + b;
  }
}

const e = new Example();
e.sum(1, 2);
// Arguments: 1,2
// Result: 3

2. Class decorators

- Class decorator는 전체 클래스 정의에 적용

- decorator 함수는 데코레이팅되는 생성자 함수인 단일 매개 변수로 호출됨

-- 생성된 클래스의 각 인스턴스가 아니라 생성자 함수에 적용됨

- 인스턴스를 조작하려면 래핑된 생성자 버전을 반환하여 직접 수행해야함

- 모든 작업은 동일한 방식으로 간단한 함수 호출 정도만 수행할 수 있기 때문에 class member decorator보다 유용하지 않음

- Class decorator로 수행하는 모든 작업은 클래스 생성자를 대체하기 위해서 새로운 생성자 함수를 반환해야함 (아래처럼)

function log(Class) {
  return (...args) => {
    console.log(args);
    return new Class(...args);
  };
}

 

예제)

@log
class Example {
  constructor(name, age) {
  }
}

const e = new Example('Graham', 34);
// [ 'Graham', 34 ]
console.log(e);
// Example {}

출처

https://github.com/tc39/proposal-decorators

https://www.sitepoint.com/javascript-decorators-what-they-are/

728x90
반응형