삽질의 현장/- TypeScript

@Decorator에 대하여 알아보자

shovelman 2019. 10. 12. 14:56

데콜레이터에 대해 알아보다가, TypeScript KR 깃허브 페이지에 번역 본이 있어 정리해봤다.

 

TypeScriptES6의 클래스가 도입됨에 따라
클래스 및 클래스 멤버에 어노테이션 또는 변경을 지원하기 위해 추가적인 기능이 필요한 일부 상황이 있다.
데코레이터는 클래스 선언과 멤버에 대한 어노테이션과 메타-프로그래밍 구문을 모두 추가할 수 있는 방법을 제공한다.

데코레이터에 대한 실험적인 지원을 사용하려면
커멘드 라인이나 tsconfig.json 에서 experimentalDecorators 컴파일러 옵션을 사용하도록 활성화해야 한다.

데코레이터는 클래스 선언, 메서드 접근제어자, 프로퍼티 또는 매개변수에 첨부될 수 있는 특별한 종류의 선언이다.
데코레이터는 @표현식의 형태로 사용하는데,
여기서 표현식은 데코레이팅된 선언에 대한 정보와 함께 런타임에 호출될 함수로 평가되어야 한다.

데코레이터는 한 줄에, 여러줄에 나눠 선언할 수 있다.

@f @g x
or
@f
@g
x

사용 예시:

function f() {
  console.log("f(): evaluated");
  return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("f(): called");
  }
}

function g() {
  console.log("g(): evaluated");
  return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("g(): called");
  }
}

class c {
  @f()
  @g()
  method() {}
}

출력 결과는 아래와 같다.

f(): evaluated
g(): evaluated
g(): called
f(): called

클래스 내의 다양한 선언에 데코레이터를 적용하는 방법에는 잘 정의된 순서가 있다.
매개 변수 데코레이터, 메서드, 접근제어자 또는 속성 데코레이터가 각 정적 멤버에 적용된다.
Method, Accessor, PropertyDecorator 등에 의한 파라미터 Decorator는 각 정적 멤버에 대해 적용된다.

  1. 메서드, 접근제어자 또는 프로퍼티 테코레이터에 이어지는 매개변수 데코레이터는 각 인스턴스 멤버에 적용된다.
  2. 메서드, 접근제어자 또는 프로퍼티 데코레이터에 이어지는 매개변수 데코레이터는 각 정적 멤버에 적용된다.
  3. 매개변수 데코레이터는 생성자에 적용된다.
  4. 클래스 데코레이터는 클래스에 적용된다.

클래스 데코레이터

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return "hello, " + this.greeting;
  }
}


function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

클래스 데코레이터는 클래스 선언 바로 직전에 선언된다.
클래스 데코레이터는 클래스 정의를 관찰, 수정 또는 바꾸는 데 사용할 수 있는 클래스 생성자에 적용된다.
클래스 데코레이터는 선언 파일이나 다른 ambient 컨텍스트 (ex. 선언 클래스)에서 사용할 수 없다.
클래스 데코레이터에 대한 표현식은 런타임에 함수로 호출되며 데코레이팅 클래스의 생성자는 대상을 유일한 인수로 호출된다.
클래스 데코레이터가 값을 반환하는 경우, 클래스 선언을 제공된 생성자 함수로 대체한다.
(새 생성자 함수를 반환하도록 선택해야 하는 경우 원본 프로토타입을 유지하도록 관리해야한다.
런타임에 데코레이터를 적용하는 로직은 이 작업을 수행하지 않는다.)

다음은 생성자를 재정의하는 방법에 대한 예제이다.

function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
  return class extends constructor {
    newProperty = "new property";
   hello = "override";
  }
}

@classDecorator
class Greeter {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}

console.log(new Greeter("world"));

메서드 데코레이터

class Greeter {
   greeting: string,
   constructor(message: string) {
     this.greeting = message;
   }
}

@enumerable(false)
greet() {
  return "hello, " + this.greeting;
}

function enumerable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.enumerable = value;
    };
}

메서드 데코레이터는 메서드 선언 바로 직전에 선언된다.
데코레이터는 메서드의 프로퍼티 Descriptor에 적용되며, 메서드 정의를 관찰, 수정 또는 바꾸는데 사용할 수 있다.
메서드 데코레이터는 선언 파일, 오버로드 또는 기타 ambient 컨텍스트(ex. 선언 클래스)에서 사용할 수 없다.
메서드 데코레이터의 표현식은 런타임에 다음 세가지 인수와 하께 함수로 호출된다.

접근제어자 데코레이터

접근제어자 데코레이터는 접근제어자 선언 바로 직전에 선언된다.
접근제어자 데코레이터는 접근제어자에 대한 프로퍼티 Descriptor에 적용되며
접근제어자 정의를 관찰, 수정 또는 바꾸는 데 사용할 수 있다.
데코레이터는 메서드의 프로퍼티 Descriptor에 적용되며 메서드 정의를 관찰, 수정 또는 바꾸는 데 사용할 수 있다.
접근제어자 데코레이터는 선언 파일이나 다른 ambient 컨텍스트 (예: 선언 클래스)에서 사용할 수 없다.

매개변수 데코레이터

class Greeter {
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }

    @validate
    greet(@required name: string) {
        return "Hello " + name + ", " + this.greeting;
    }
}

import "reflect-metadata";

const requiredMetadataKey = Symbol("required");

function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
    let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
    existingRequiredParameters.push(parameterIndex);
    Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}

function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) {
    let method = descriptor.value;
    descriptor.value = function () {
        let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);
        if (requiredParameters) {
            for (let parameterIndex of requiredParameters) {
                if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) {
                    throw new Error("Missing required argument.");
                }
            }
        }

        return method.apply(this, arguments);
    }
}

매개변수 데코레이터는 매개변수 선언 바로 직전에 선언된다.
메개변수 데코레이터는 클래스 생성자 또는 메서드 선언의 함수에 적용된다.
매개변수 데코레이터는 선언 파일, 오버로드 또는 기타 ambient 컨텍스트(ex. 선언클래스)에서 사용할 수 없다.
매개변수 데코레이터의 표현식은 런타임에 다음 세가지 인수와 함께 호출된다.

  1. 정적 멤버에 대한 클래스의 생성자 함수 또는 인스턴스 멤버에 대한 클래스의 프로토타입
  2. 멤버의 이름
  3. 함수의 매개 변수 목록 내에 매개 변수의 서수(순서가 있는) 인덱스

 

참고: typescript-kr