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 객체는 나중에 다뤄보도록하자.
아래 포스트에서 별도로 다루고 있다.
한편, 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.js example
github.com/yogae/inversify-example/tree/master/getting_started
inversify.js를 사용해 본 포스트
studystorage.blogspot.com/2017/07/typescript-express-inversify-js-ioc.html
설치 및 세팅
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
사용법
안타깝게도 한국 포스팅, 영상 등에서 친절하게 설명한 곳이 없다.
다음 외국 포스팅을 보고 정리해보았다.
blog.wotw.pro/typescript-decorators-reflection/
- 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
'Programming Language > 🟦 Typescript' 카테고리의 다른 글
typescript에서 DOM 다루기 (0) | 2021.03.01 |
---|---|
Mapped Type (0) | 2021.03.01 |
@Decorator (2) : Decorator 예시 보며 활용 방법 익히기 (0) | 2021.01.13 |
@Decorator (1) : setting, decorator signature, decorator factory (0) | 2021.01.13 |
ts 환경에서 외부 라이브러리 사용시 주의할 점 (+ typeRoots, esmoduleinterop) (0) | 2020.11.21 |