Typescript

[Typescript] decorator, reflect-metadata, javascript Reflect

prefer2 2023. 11. 5. 22:57

 

최근 BFF코드를 작성하며 익숙하지 않은 문법을 접했다. 자바의 annotaion을 떠오르게 하는 문법이었는데 알고보니 Typescript의 decorator라는 문법이었다. 아직Typescript에서 실험적인 기능이기는 하나, 의존성 주입이 필요한 경우에 꽤나 꽨찮은 구성 방법인 것 같아 내용을 정리해보고자 한다.

현재(23년 10월) 기준 5.2 버전에서 정식으로 제공하고 있다

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#decorator-metadata

이전 버전에서는 실험적인 기능이기 때문에 tsconfig.json에서 experimentDecorators 컴파일러 옵션을 활성화해줘야 한다.

{
  "compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true
  }
}

 

데코레이터는 다음에 적용될 수 있는 함수이다.

  • class
  • class property
  • class method
  • class accessor
  • class method parameter

데코레이터를 사용하는 것은 HOC와 같이 함수를 합성하는 방법이다. 값을 감싸고 대체함으로서 meta-program이 가능하도록 해준다. @expression 형식을 사용하고, expression은 데코레이팅 된 선언에 대한 정보와 함께 런타임에 호출되는 함수다. 함수를 작성하고 해당 함수를 @와 함께 사용하면 원하는 곳에 해당 데코레이터가 적용된다.

function simpleDecorator() {
  console.log('---hi I am a decorator---')
}

@simpleDecorator
class A {}

 

데코레이터는 class의 다양한 곳에서 사용될 수 있다.

@classDecorator
class Bird {
  @propertyDecorator
  name: string;

  @methodDecorator
  fly(
    @parameterDecorator
      meters: number
  ) {}

  @accessorDecorator
  get egg() {}
}

 

앞에서 언급했듯 데코레이터는 런타임에 호출되는 함수기 때문에, 런타임에 클래스 정의가 평가될 때 한 번만 실행된다. 예를 들어 아래와 같은 코드는 클래스 A의 새 인스턴스를 초기화(new A())한 적이 없어도, 데코레이터 적용을 로깅한다.

function f(C) {
  console.log('apply decorator')
  return C
}

@f
class A {}

// output: apply decorator

데코레이터에 대한 자세한 문법은 공식 문서에 매우 상세하게 잘 나와있기 때문에 문서를 참고해보자. 문법적인 내용보다는 이걸 어디다 써먹는지에 집중해 보고자 한다

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

 

 


 

reflect-metadata

메타데이터는 데이터에 대한 데이터이다(?!?) 예를 들어 배열의 길이는 배열의 메타데이터다. 또, 배열의 요소들은 배열의 데이터지만, 그 데이터의 타입은 메타데이터다. 메타데이터는 실제로 프로그래밍에 영향을 미치지는 않지만 우리가 프로그래밍을 하기에 쉽게 도와준다

사실 뭐가 메타데이터다 라고 명확하게 나누는 것은 어려운 일이다. 어쩌면 데이터의 데이터라는 말이 가장 훌륭한 설명인 것 같다. 나는 해당 단어를 들었을 때 HTML의 meta 태그가 가장 먼저 떠올랐는데 해당 태그는 페이지에 나타나지는 않으면서 페이지에 대한 정보를 나타낸다는 뜻에서 이도 하나의 메타데이터이다.

 

reflection(물에 비친 상같은 것을 reflection이라고 부른다)은 기존 언어에 깔려있는 메커니즘을 바꾸는 프로세스를 의미한다. javascript에서 Object.defineProperty()를 통해 메서드의 기본 속성들을 바꾸는 것도 일종의 reflection이다.

javascript ES6에서 reflection과 이름이 비슷한 Reflect라는 객체를 제공한다. 다른 global object들과 다르게 Reflect는 constructor가 아니기 때문에 new Reflection()과 같은 형식으로 사용하지 못한다. Math object처럼 Reflect의 속성과 메서드는 static하다. Reflect는 내부 메서드를 호출하는데 사용된다.

const p = new Proxy(
  {},
  {
    deleteProperty(targetObject, property) {
      // Custom functionality: log the deletion
      console.log("Deleting property:", property);

      // Execute the default introspection behavior
      return Reflect.deleteProperty(targetObject, property);
    },
  },
);

위 코드에서 보듯 해당 속성을 제거하여도 Reflect를 통해 기존에 가지고 있던 속성을 호출할 수 있다.

 

decorator에 대해 알아보다 갑자기 metadata와 reflection에 대해 알아본 이유는 reflect-metadata라는 패키지가 어떤 역할을 하는지 궁금해서다. reflection을 하기 위해서는 metadata와 decorator를 함께 사용해주어야 한다. 해당 패키지는 reflection을 할 수 있도록 도와준다. 구현 코드를 보면 javascript의 Reflect api를 사용하고 있음을 확인할 수 있다.

아래는 reflect-metadata의 목표이다. 내가 번역하는 것보다 영어로 보는게 의미 전달이 잘 될 것 같아 남겨본다.

  • A number of use cases (Composition/Dependency Injection, Runtime Type Assertions, Reflection/Mirroring, Testing) want the ability to add additional metadata to a class in a consistent manner.
  • A consistent approach is needed for various tools and libraries to be able to reason over metadata.
  • Metadata-producing decorators (nee. "Annotations") need to be generally composable with mutating decorators.
  • Metadata should be available not only on an object but also through a Proxy, with related traps.
  • Defining new metadata-producing decorators should not be arduous or over-complex for a developer.
  • Metadata should be consistent with other language and runtime features of ECMAScript.

 


 

행단 관심사, 의존성 분리, AOP….

데코레이터는 비즈니스와 상관 없는 로직들을 숨기면서 기능을 변경하거나 확장할 수 있게 해준다. 또 클래스간의 공통 관심사가 있을때 데코레이터를 통해 중복된 코드를 줄이고 코드를 모듈 단위로 관리할 수 있게 된다.

아직 횡단 관심사 분리, 의존성 분리와 같은 방법에 대해서 잘 알지는 못하지만 nest.js의 데코레이터와 기존 코드들을 보며 어떻게 관심사 분리를 하는 것이 좋을지 고민해보면 좋을 것 같다. AOP와 같은 개념은 spring과 프레임워크에서 중요하게 사용되어 프론트엔드 개발자로서는 크게 중요성을 느끼지 못하고 있었는데 데코레이터를 학습해보니 관련해서도 학습을 해야겠다는 생각이 든다.

이번 글에서는 decorator가 무엇인지 그리고 컴퓨터과학에서 metadata와 reflection에 대해 알아보다보니 행단 관심사, 의존성 분리, AOP와 같은 (이번에 다 파악하기에는 꽤나 헤비한) 주제들에 대해서는 깊이 있게 알아보지는 못했다. 아래 글은 typescript에서의 AOP에 대해 작성된 글인데 한번 읽어보기 좋아 추천한다

https://d2.naver.com/helloworld/3010710

 


 

참고

https://mirone.me/a-complete-guide-to-typescript-decorator/

https://www.npmjs.com/package/reflect-metadata

https://rbuckton.github.io/reflect-metadata/

https://medium.com/jspoint/introduction-to-reflect-api-for-metaprogramming-in-javascript-8b5a2bb84282

https://medium.com/jspoint/a-brief-introduction-to-metaprogramming-in-javascript-88d13ed407b5

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect

https://docs.nestjs.com/custom-decorators

https://toss.tech/article/nestjs-custom-decorator

반응형

'Typescript' 카테고리의 다른 글

[Typescript] Template Literal Types  (0) 2022.10.02
[typescript] ts-loader와 babel-loader  (0) 2022.09.25
[Typescript] 타입은 값들의 집합  (0) 2022.09.18