데코레이터(Decorators)

김동현·2022년 5월 24일
0

TypeScript

목록 보기
16/18
post-thumbnail

데코레이터(Decorators)

데코레이터는 메타 프로그래밍(Meta-Programming)하는 데 유용하게 사용됩니다. 데코레이터는 말 그대로 코드 조각을 장식해주는 역할을 하며 타입스크립트에서는 그 기능을 함수로 구현할 수 있습니다.

먼저 데코레이터를 사용하기 위해서 tsconfig.json에서 "experimentalDecorators" 옵션을 true로 설정해주어야 합니다.

데코레이터는 "클래스", "메서드", "접근자", "프로퍼티", "파라미터"에 적용할 수 있습니다. 데코레이터는 @expression 형식으로 적용하는데, 여기에서 expression은 반드시 함수여야 합니다.

클래스 데코레이터

먼저 클래스에 데코레이터를 적용한 예를 살펴보겠습니다.

Logger이라는 함수를 선언하고 클래스 선언문 앞에 @Logger을 작성하여 클래스에 데코레이터를 적용할 수 있습니다.

이때 Logger 함수는 하나의 인수를 전달받는데 데코레이터를 적용한 클래스의 생성자 함수를 전달받습니다.

위 예제의 실행 결과는 아래와 같습니다.

클래스 데코레이터 함수는 명시적으로 호출하지 않아도 클래스 선언문이 평가되기 직전에 실행됩니다. 즉, 클래스를 호출하여 인스턴스를 생성하기 전에 먼저 호출이 되며, 이후 인스턴스를 다시 생성하더라도 데코레이터는 다시 실행되지 않습니다.

우리는 데코레이터 함수가 전달받는 클래스 생성자 함수를 통해 함수 내부에서 new 연산자와 함께 호출하여 인스턴스를 생성할 수도 있습니다.


클래스에 적용한 데코레이터 함수의 반환값으로 새로운 클래스 생성자 함수를 작성할 수 있습니다. 이때 데코레이터 함수의 반환값에 작성된 클래스 생성자 함수는 기존 클래스 생성자 함수를 대체하게 됩니다.

반환값으로 클래스를 작성할 때 익명 클래스 정의를 사용하며 데코레이터 함수가 인수로 전달받는 기존 클래스 생성자 함수를 extends하여 작성합니다. 만약 extends하지 않는다면 기존 클래스에서 정의한 프로퍼티를 잃어버리기 때문에 extends하여 기존 프로퍼티를 유지하도록 합니다.

WithTemplate 테코레이터 팩토리는 반환값으로 데코레이터 함수 정의를 작성했습니다. 클래스에 적용된 데코레이터의 경우 인수로 클래스 생성자 함수를 인수로 전달받아 호출됩니다.

그리고 클래스에 적용된 데코레이터 함수는 반환값으로 새로운 클래스 생성자 함수를 작성하여 기존 클래스를 대체할 수 있습니다.
반환값으로 클래스 생성자 함수 작성할 때 인수로 전달받은 기존 클래스 생성자 함수를 extends하여 정의한다면 기존 클래스의 constructor에 정의한 프로퍼티를 유지하여 클래스를 확장하는 것처럼 사용할 수 있습니다.

위 예제에서는 originalConstructor이 기존 클래스 생성자 함수를 전달받는 매개변수가 됩니다.

데코레이터 함수에 제네릭 타입 T를 작성하고 extends를 사용하여 매개변수 originalConstructor이 생성자 함수임을 명시하도록 했습니다.

이때 WithTemplate 데코레이터 팩토리는 클래스 정의가 평가될 때 실행되고 이후 반환값에 작성한 데코레이터 함수가 차례로 실행됩니다.
데코레이터 함수의 반환값에 작성된 클래스로 기존 클래스가 대체되고 이후 클래스를 new 연산자와 함께 호출시 기존 클래스의 constructor가 아닌 데코레이터 함수의 반환값에 작성된 클래스의 constructor 메서드가 실행되어 인스턴스를 생성하게 됩니다.

데코레이터 팩토리

데코레이터 팩토리는 데코레이터 함수를 반환하는 함수로 정의합니다.

Logger 함수의 반환값으로 데코레이터 함수를 익명 함수로 정의하고 이를 반환값으로 작성해줍니다. 반환값으로는 기존 데코레이터 함수처럼 클래스 생성자 함수를 인수로 전달받습니다. 그리고 데코레이터를 적용할 클래스에 @Logger()처럼 호출문으로 작성해주어야 합니다.

그러면 Logger 함수가 실행되고 Logger 함수의 반환값으로 작성된 데코레이터 함수가 실행된 다음 클래스가 평가됩니다.

반환값으로 데코레이터 함수를 정의한다면 인수를 전달하여 데코레이터 함수에서 사용할 수 있습니다. 위 코드처럼 @Logger('hello')를 작성하여 팩토리 함수인 Logger 함수에게 인수로 전달하여 데코레이터 함수 내에서 사용할 수 있습니다.


아래는 데코레이터 팩토리를 이용한 예제입니다.

WithTemplate 데코레이터 팩토리가 반환하는 데코레이터 함수는 인수로 클래스 생성자 함수를 전달받기 때문에 함수 내부에서 constructor 메서드를 생성자 함수로 호출하여 인스턴스를 생성할 수 있습니다.
생성된 인스턴스 내부에는 name 프로퍼티가 존재하며 name 프로퍼티 값을 이용하여 돔 요소 노도를 조작할 수도 있습니다.

이처럼 데코레이트는 개발자에게 추가적인 유틸리티를 제공하여 주어진 클래스에 관한 화면에 어떤 것을 렌더할 수 있습니다.

데코레이터 합성

선언에 여러 데코레이터를 적용할 수 있습니다. 이때 데코레이터 팩토리의 호출은 위에서 아래로 데코레이터 함수는 아래서 위 순서로 호출됩니다.

Person 클래스에 @Logger('hello')@WithTemplate('<h1>My Person Object</h1>, 'container')을 적용한다면 다음 순서대로 함수가 호출됩니다.

  1. Logger 데코레이터 팩토리 함수 호출

  2. WithTemplate 데코레이터 팩토리 함수 호출

  3. WithTemplate 데코레이터 팩토리의 반환값인 데코레이터 함수 호출

  4. Logger 데코레이터 팩토리의 반환값인 데코레이터 함수 호출

중요한 것은 데코레이터 팩토리가 반환하는 데코레이터 함수가 아래서 위로 역순으로 실행된다는 점입니다.

프로퍼티 데코레이터

데코레이터 함수를 프로퍼티에도 적용할 수 있습니다. 이때 데코레이터 함수는 인수는 아래와 같습니다.

  1. 정적 프로퍼티에 적용한 경우 클래스 함수, 인스턴스 프로퍼티의 경우 클래스의 prototype 프로퍼티에 바인딩된 객체

  2. 프로퍼티 키 값을 문자열 값으로

위 코으데서는 Product 클래스에 정의한 title 프로퍼티에 Log 데코레이터 함수를 적용했습니다.

title 프로퍼티에 적용한 Log 데코레이터 함수는 인수로 Product 클래스의 prototype 프로퍼티에 바인딩된 프로토타입 객체와 프로퍼티 키 값인 title을 문자열로 전달받아 실행됩니다.

첫 번째 인수로 전달받는 객체는 constructor 함수와 getPriceWithText 메서드, pirce 접근자 프로퍼티가 존재하는 것을 보아 프로토타입 객체임을 확인할 수 있고, 두 번째 인수로 전달된 propertyName에는 프로퍼티 키 값이 문자열로 전달된 것을 확인할 수 있습니다.

프로퍼티에 적용한 데코레이터 함수는 인스턴스를 생성하기 전 클래스 정의가 평가될 때 실행됩니다.

프로프티에 적용한 데코레이터 함수는 반환값이 무시됩니다.

접근자 프로퍼티 데코레이터

프로퍼티 이외 접근자 프로퍼티에도 데코레이터를 적용할 수 있습니다.
접근자 프로퍼티에 적용하는 경우 데코레이터 함수가 전달받는 인수는 아래와 같습니다.

  1. 정적 프로퍼티의 경우 클래스, 인스턴스 프로퍼티의 경우 클래스의 prototype 프로퍼티에 바인딩된 객체

  2. 프로퍼티 키 값을 문자열 값

  3. 프로퍼티 디스크립터 객체

Product 클래스 내 price 접근자 프로퍼티에 Log2 데코레이터 함수를 적용했습니다.

Log2 데코레이터 함수는 인수로 프로토타입 객체와 프로퍼티 키 값, 디스크립터 객체를 전달받아 호출됩니다.


접근자 프로퍼티에 적용한 데코레이터 함수의 반환값으로 프로퍼티 디스크립터 객체를 작성할 수 있으며 이때 작성된 디스크립트 객체로 기존 디스크립터 객체를 대체합니다.

메서드 데코레이터

메서드에도 데코레이터를 적용할 수 있습니다. 메서드에 적용되는 데코레이터 함수가 전달받는 인수는 아래와 같습니다.

  1. 정적 메서드인 경우 클래스, 프로토타입 메서드인 경우 클래스의 prototype 프로퍼티에 바인딩된 객체

  2. 메서드 이름을 문자열 값으로

  3. 프로퍼티 디스크립터 객체

결과는 아래처럼 출력됩니다.


메서드에 적용한 데코레이터 함수의 경우 메서드의 새로운 프로퍼티 디스크립터 객체를 반환할 수 있습니다. 즉, 반환값으로 작성된 프로퍼티 디스크립터 객체로 기존 디스크립터 객체를 대체하게 됩니다.

매개변수 데코레이터

매개변수에도 데코레이터 함수를 적용할 수 있습니다. 매개변수에 적용된 데코레이터 함수가 전달받는 인수는 아래와 같습니다.

  1. 매개변수를 사용하는 메서드가 정적 메서드인 경우 클래스, 프로토타입 메서드인경우 클래스의 prototype 프로퍼티에 바인딩된 객체

  2. 매개변수가 선언된 메서드의 이름을 문자열 값으로

  3. 매개변수의 위치를 숫자값으로(첫 번째라면 0부터 시작)

getPriceWithText 메서드의 tax 매개변수에 Log4 데코레이터 함수를 적용했습니다.

첫 번째 인수로 프로토타입 객체, 두 번째 인수로 매개변수를 사용하는 메서드 이름, 마지막으로는 매개변수의 위치 , 즉 0이 전달된 것을 확인할 수 있습니다.

참고로 메서드에도 데코레이터를 적용한 경우 매개변수에 적용한 데코레이터 함수가 먼저 실행된 다음 메서드 데코레이터 함수가 실행됩니다. 즉, 위 예제에서는 Log4 데코레이터 함수가 실행된 다음 Log3 데코레이터 함수가 실행됩니다.

매개변수에 적용한 데코레이터 함수의 반환값에 작성된 값은 무시됩니다.

데코레이터 실행 순서

데코레이터 함수는 명시적으로 호출하지 않습니다. 데코레이터 함수는 프로퍼티나 메서드, 매개변수 등 선언 직전에 자동적으로 호출됩니다(단, 데코레이터 팩토리의 경우 호출문으로 작성해주어야 합니다).
호출될 때 적용된 위치(클래스, 프로퍼티, 접근자 프로퍼티, 메서드, 매개변수)에 따라서 데코레이터 함수에게 전달되는 인수가 결정됩니다.

주의할 점이 인스턴스를 생성하지 않아도 ,즉 클래스를 명시적으로 호출하지 않아도 호출되는 함수입니다. 이때 데코레이터 함수는 한 번만 실행되며 이후 인스턴스를 생성하기 위해 클래스를 다시 호출하더라도 데코레이터 함수는 다시 호출되지 않습니다.

데코레이터는 클래스 정의가 평가될 때 추가적인 셋업 작업을 합니다. 미리 만들어진 데코레이터를 통해 보이지 않게 추가 기능을 더할 수 있습니다.

profile
Frontend Dev

0개의 댓글