본문으로 바로가기

@Decorator (3) : reflect-metadata

category Programming Language/🟦 Typescript 2021. 1. 13. 06:32

typescript-kr.github.io/pages/decorators.html - 메타데이터 (Metadata) 부분을 참고합시다.

www.npmjs.com/package/reflect-metadata

 

reflect metadata에 들어가기에 앞서...

 

왜 쓰냐?는 질문에 runtime reflection을 하게 해준다는 단순한 답을 얻을 수 있었다.

reflecting이란 코드가 구동시(런타임) 자기 자신을 검사, 수정할 수 있는 능력을 말한다.

reflection programming is the ability of a process to examine, introspect, and modify its own structure and behavior.[
출처 : https://en.wikipedia.org/wiki/Reflective_programming

 

vanilla javascript에서도 Reflect는 기본 내장 객체로 존재한다. Reflect 객체는 나중에 다뤄보도록하자.

아래 포스트에서 별도로 다루고 있다.

darrengwon.tistory.com/1133

 

 

한편, refelct-metadata는 일반 Reflect의 기능을 강화한, 컴파일 타임에 데코레이터에 메타데이타를 전달할 수 있도록 하는 기능을 추가해주는 pollyfill이라고 이해하면 된다.

reflect-metadata Allows you to do runtime reflection on types.
The native (non reflect-metadata) version of type inference is much poorer than reflect-metadata and consists only of typeof and instanceof.

출처 : https://stackoverflow.com/questions/45135878/what-is-reflect-metadata-in-typescript

 

reflection은 여러 가지 유스 케이스 (Composition / DI, Run-time Type Assertions, Testing)에 유용하다.

자체적인 DI를 만들려고 할 때 사용되고, Nest에서도 DI를 위해 사용하는 것로 알려져 있다.

그래서 DI를 TS 기반에서 이해하려면 decorator와 reflection에 대한 이해가 필요합니다

 

참고  데코레이터 메타 데이터는 실험적인 기능으로 향후 릴리스에서 주요 변경 사항이 있을 수 있습니다.
그러나 제가 확인한 바 21 May 2019부터 아무런 수정 사항이 없었습니다...

 

여담으로 TS 기반 DI 관련 라이브러리 중 IoC 만들 때 유명한 툴로 InversifyJS가 있더군요?

Nest 안 쓰면 이런 걸 써봄직 합니다.

 

github.com/inversify/InversifyJS

 

inversify/InversifyJS

A powerful and lightweight inversion of control container for JavaScript & Node.js apps powered by TypeScript. - inversify/InversifyJS

github.com

 

inversify.js example

github.com/yogae/inversify-example/tree/master/getting_started

 

inversify.js를 사용해 본 포스트

studystorage.blogspot.com/2017/07/typescript-express-inversify-js-ioc.html

yogae.tistory.com/11

 

 

설치 및 세팅

pollyfill이다. 따라서 runtime시에 reflect-metadata를 사용하게 만들어 주어야 한다.

npm i reflect-metadata
yarn add reflect-metadata

tsconfig.json에서 emitDecoratorMetadata를 true로 설정해주어야 사용할 수 있다.

/* Experimental Options */
"experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
"emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */

 

emitDecoratorMetadata 는 아래 3 프로퍼티를 추가합니다.

살펴보니 전부다 타입을 반환하네요.

사용법에서 이 프로퍼티들을 출력해보겠습니다.

 

  • design:type: 데코레이터가 적용된 객체의 타입
  • design:paramtypes: 데코레이터가 적용된 곳의 파라미터의 타입을 배열로 반환.
  • design:returntype: 데코레이터가 적용된 함수가 반환해야 하는 return 타입을 반환

 

 

 

reflect metadata API

사용할 수 있는 Reflect 메서드들을 전부 나열했습니다.

메서드 이름을 살펴보면, 해당 meatadata 정의, 존재 여부, get, delete로 직관적으로 사용할 수 있음을 확인할 수 있으니 들어가서 훑어보기라도 합시다.

 

github.com/rbuckton/reflect-metadata#api

 

rbuckton/reflect-metadata

Prototype for a Metadata Reflection API for ECMAScript - rbuckton/reflect-metadata

github.com

 

사용법

안타깝게도 한국 포스팅, 영상 등에서 친절하게 설명한 곳이 없다.

다음 외국 포스팅을 보고 정리해보았다.

blog.wotw.pro/typescript-decorators-reflection/

 

TypeScript Decorators: Reflection

This post takes a cursory look at reflection with TypeScript. Its primary focus is how reflection can be used with TypeScript decorators. It introduces Reflect, reflect-metadata, and some miscellaneous related components.

blog.wotw.pro

 

 

- Defining new metadata, getMetadata

 

reflect-metadata provides both imperative commands and a decorator factory. The decorator factory stores the metadata key-value pair and passes control through.

 

import "reflect-metadata";

class BasicUsage {
  constructor() {
    // class의 constructor 내부에 명시적으로 metadata를 정의하기
    // key, value, target, propertyKey
    Reflect.defineMetadata("foo1", "bar1", this, "baz");
  }

  // decorator factory 방식으로 정의하기
  // key, value
  @Reflect.metadata("foo2", "bar2")
  public baz() {}
}

const demoBasicUsage = new BasicUsage();

// key, target, propertyKey
console.log(Reflect.getMetadata("foo1", demoBasicUsage, "baz")); // bar1

console.log(Reflect.getMetadata("foo2", demoBasicUsage, "baz")); // bar2

 

method decorator에서 Reflect.getMetadata를 이용하여 tsconfig에서 emitDecoratorMetadata 옵션을 켜서 추가된 옵션들을 출력해보겠습니다.

import "reflect-metadata";

function LogMethod(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
  // key, target, propertyKey

  // 데코레이터가 적용된 객체. 여기서는 함수 객체가 반환됨.
  // function Function() { [native code] }
  console.log(Reflect.getMetadata("design:type", target, propertyKey));

  // 데코레이터가 적용된 메서드의 파라미터들을 배열로 반환.
  console.log(Reflect.getMetadata("design:paramtypes", target, propertyKey)[0]);

  // return 하는 값의 타입.
  // string을 반환한다고 해서 string을 뱉는게 아니라, 타이핑이 string으로 되어 있어야 string을 뱉음
  console.log(Reflect.getMetadata("design:returntype", target, propertyKey));
}

class Demo {
  @LogMethod
  public foo(bar: number): string {
    return "hello";
  }
}

 

 

예시

 

<reflect-metadata example 1>

 

import "reflect-metadata";

function validate<T>(target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<T>) {
  const set = descriptor.set!;
  console.log(set);
  descriptor.set = function (value: T) {
    const type = Reflect.getMetadata("design:type", target, propertyKey);
    console.log(type);
    console.log(value);
    if (!(value instanceof type)) {
      throw new TypeError("Invalid type.");
    }
    set.call(target, value);
  };
}

class Test1 {}

class Test2 {
  private _member!: Test1;

  @validate // 나중에 실행됨
  @Reflect.metadata("design:type", Test1) // 먼저 실행됨
  set member(value: Test1) {
    this._member = value;
  }

  get member(): Test1 {
    return this._member;
  }
}

const test2 = new Test2();
console.log("1", test2.member);
test2.member = new Test1();
console.log("2", test2.member);
test2.member = new Test2();
console.log("3", test2.member);

 

 

 

참고하면 좋은 글들)

 

owenjeon.github.io/2018/05/13/relect-metadeta/

blog.wolksoftware.com/decorators-metadata-reflection-in-typescript-from-novice-to-expert-part-4

blog.wotw.pro/typescript-decorators-reflection/


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체