카테고리 없음

[TypeScript] Decorator

Bonita SY 2020. 10. 21. 20:04
728x90
반응형

Decorator

- TypeScript와 ES6의 Class 도입으로 클래스 및 클래스 멤버에 주석을 달거나 수정하는 것을 지원하기 위해 도입

- 클래스 선언과 멤버에 대한 주석과 메타 프로그래밍 구문을 모두 추가할 수 있는 방법을 제공

- JavaScript에 대한 stage 2 proposal

- 향후 변경될 가능성 有

- @expression 으로 사용

-- expression은 decorate된 선언(declaration)에 대해 정보와 함께 런타임에 호출될 함수를 평가(evaluate)함

 

예제

// @sealed 라는 decorator 생성
function sealed(target) {
  // do something with 'target' ...
}

 

- 선언에 적용할 decorator를 커스터마이징학 싶으면 decorator factory를 사용

- decorator factory는 런타임에 decorator에 의해서 호출될 함수를 return하는 함수

 

예제

// decorator factory
function color(value: string) {
  // this is the decorator factory
  return function (target) {
    // this is the decorator
    // do something with 'target' and 'value'...
  };
}

 

Decorator 구성(Composition)

- 1개의 선언에 복수개의 decorator를 사용 가능

 

한줄에 쓰기

@f @g x

 

여러줄에 쓰기

@f
@g
x

 

- 이 때 이들의 evaluation은  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

- 각 decorator의 evaluate는 위에서 아래로 (top-to-bottom)

- 결과는 아래에서 위로(bottom-to-top)

 

Decorator Evaluation

- instance 멤버에 대해 함수(Function), 접근자(Accessor), 속성(Proeprty), 매개변수(Parameter) decorator가 적용

- static 멤버에 대해 함수(Function), 접근자(Accessor), 속성(Proeprty), 매개변수(Parameter) decorator

- 생성자(Constructor)에는 parameter decorator가 적용

- Class에는 Class decorator가 적용

 

Class Decorator

- class 선언 직전에 선언

- class 생성자에 적용됨

- class 정의(definition)를 관찰하거나 수정될 때 사용 가능

- 선언한 class 내에서 사용 불가

- class decorator 내에 작성된 expression은 런타임에 함수처럼 사용됨

- decorate된 클래스의 생성자는 class decorator의 argument

 

예제

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"));

 

Method Decorator

- 함수 선언 직전에 선언

- Class Decorator와 마찬가지로 함수 정의를 관찰하거나 수정할 때 사용

- mothod decorator 내 expression은 런타임에 함수처럼 호출

- ES5보다 낮은 JavaScript인 경우 무시

 

예제

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;
  };
}

 

Accessor Decorator

- accessor 선언 바로 직전에 선언

- accessor의 property descriptor에 적용됨

- accessor의 정의를 관찰, 수정하는 데 사용

- 선언 파일과 선언 클래스에서 사용 불가

- accessor decorator의 expression은 런타임에 1. 정적 멤버에 대한 클래스의 생성자 함수 또는 인스턴스 멤버에 대한 클래스의 프로토타입, 2. member name, 3. member의 property decorator가 arguement로 주어짐

- accessor decorator가 값을 반환하면 member의 proeprty descriptor로 사용됨

- ES5 미만의 JavaScript인 경우 무시

 

예제

class Point {
  private _x: number;
  private _y: number;
  constructor(x: number, y: number) {
    this._x = x;
    this._y = y;
  }

  @configurable(false)
  get x() {
    return this._x;
  }

  @configurable(false)
  get y() {
    return this._y;
  }
}

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

 

Property Decorator

- property 선언 직전에 선언

- 선언 파일이나 선언 클래스에서 사용 불가

- proeprty decorator의 expresion은 1. 클래스의 프로토타입, 2. member name 을 argument로 사용하여 런타임에 함수로 호출됨

- property decorator는 특정 이름의 송석이 클래스에 대해 선언이 되었는지 관찰하는 데만 사용 가능

 

 

예제

class Greeter {
  @format("Hello, %s")
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    let formatString = getFormat(this, "greeting");
    return formatString.replace("%s", this.greeting);
  }
}

import "reflect-metadata";

const formatMetadataKey = Symbol("format");

function format(formatString: string) {
  return Reflect.metadata(formatMetadataKey, formatString);
}

function getFormat(target: any, propertyKey: string) {
  return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

 

Parameter Decorator

- parameter 선언 직전에 선언

- class 생성자 또는 함수 선언을 위한 함수에 적용됨

- 선언 파일, 선언 클래스에 사용 불가

- 매개변수가 함수에서 선언되었는지 확인하기 위해서만 사용 가능

- parameter decorator의 expression은 런타임에 1. 정적 멤버에 대한 클래스의 생성자 함수 또는 인스턴스 멤버에 대한 클래스의 프로토타입, 2. member name, 3. parameter의 ordinal index 라는 argument와 함께 함수로 호출

- parameter decoratore의 반환 값은 무시됨

 

예제

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);
  };
}

 

출처

https://www.typescriptlang.org/docs/handbook/decorators.html

728x90
반응형