타입스크립트와 데코레이터 - Typescript with Decorator
Typescript 의 데코레이터를 이해해 보자
타입스크립트로 작성하는 프레임워크, NestJS 와 같은 프레임워크는 Typescript 를 거의 중점적으로 사용한다.
이 과정에서, Typescript 에서 제공하는 Decorator(데코레이터) 를 사용하게 된다.
모든 프레임워크의 공식 문서가 그렇듯, 프레임워크의 유입과 점유율을 위해,
깊게 들어가지 않고 마치 핸드북처럼 간편하게 알려주는 것이 트렌드인 것 같다.
나는 공식 문서를 통해 공부하는 스타일이다.
NestJS 는 애너테이션을 통해 DI, IoC, AOP 의 틀을 만들었다.
DI
- Dependency Injection : 의존성 주입IoC
- Inversion Of Control : 제어 역전AOP
- Aspect Oriented Programming : 관점 지향 프로그래밍
Express 는 컨트롤러, 서비스, 레포 단의 레이어를 통합하여 메서드 하나로 퉁칠수도 있지만,
반대로 이는 사용자에게 코드 구조를 자유롭게 만들기도 한다.
역으로 모든 Express 구조는 사용자의 프로젝트마다 다를 수 있음을 의미하기도 한다.
NestJS 또한 코드 폴더의 자유가 존재한다.
하지만, 데코레이터의 편리함과 NestJS 의 모듈식 의존성 주입은 감히 그 구조를 어기고 맘대로 꾸미기는 어렵다고 생각한다.
NestJS 는 위에서 말한 DI 를 통한 "싱글톤" 체제를 유지하여 메모리를 아낄 수 있다고 말하기도 하지만,
나는 NestJS 의 강점은, 정해진 구조를 통해, 일관된 코드 reading 과 대규모 프로젝트 manage 라고 생각한다.
잡설은 여기까지 하고, NestJS 에서 제공하는 데코레이터의 편안함에 묻혀,
나는 감히 프레임워크에게만 의지하기만 했었다.
예를 들면, @Controller()
라고 선언하면, NestJS 가 알아서 컨트롤러라고 인식 할 거야! 와 같다.
NestJS 에서는 데코레이터가 정말 많다. 해당 데코레이터가 NestJS 에서 어떤 역할을 하는지 익히기도 바쁘다고 생각했지만,
이제 와서 드는 생각은, 데코레이터는 정말로 어떤 역할을 하는가?, 데코레이터를 통해 어떻게 DI 하지? 였다.
다행히도, 타입스크립트 핸드북 사이트에서 이를 잘 알려주고 있었다.
https://www.typescriptlang.org/ko/docs/handbook/decorators.html
이를 통해 클래스, 메서드, 접근자, 프로퍼티, 매개변수 에 할당된 역할을 알아 볼 것이다.
데코레이터란 - Decorator Intro
데코레이터란 무엇일까?
데코레이터는 단순히 말한다면, 기존 객체(모든 것을 포함) 에 속성을 "추가" 하거나,
변경할 수 있게 만들어 준다.
flowchart LR
Object(("어떠한 객체"))
Decorator["데코레이터"]
subgraph Result ["데코레이터"]
DecoResult(("데코레이터에 의해 <br/> 기능이 추가,변경 된 객체"))
end
Object --> Decorator
Decorator --> Result
Javascript 에서도 사용이 가능하나, Node.js 엔진 자체에서 지원해 주는 기능이 아니다.
따라서, Babel 을 이용한 트랜스파일 기능을 이용해야 사용 할 수 있다.
즉, 현재는 아예 Node.js 에 추가된 기능은 아니므로 실험적 기능이라는 것이다.
function testDeco(target, key, descriptor) {
return function () {
console.log(target, key, descriptor);
}
}
@testDeco()
class TestClass {
a = 1;
b = "b";
c = function () {
console.log("function c");
}
};
const instance = new TestClass();
Result :
➜ test-code node decorator-js.js
/Users/gongdamhyeong/intelliJ/udemy/docs-to-learn/test-code/decorator-js.js:7
@testDeco()
^
SyntaxError: Invalid or unexpected token
....
Node.js v23.10.0
Javascript 로 바벨 및 언어 버전 설정이 가능하지만,
Typescript 에서의 정확한 데코레이터 기능을 알아보기 위해서이므로,
Typescript 로 데코레이터를 정확하게 배워보자.
Typescript 로 작성해 보기
function testDeco(target: any) {
console.log("데코레이터 타겟의 이름 :", target.name);
}
@testDeco
class MyClass {
constructor() {
console.log("MyClass 인스턴스 생성됨.");
}
}
const instance = new MyClass();
Result :
➜ test-code ts-node decorator-ts.ts
/usr/local/lib/node_modules/ts-node/src/index.ts:859
return new TSError(diagnosticText, diagnosticCodes, diagnostics);
^
TSError: ⨯ Unable to compile TypeScript:
decorator-ts.ts:6:7 - error TS1219: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning.
tsconfig.json
파일을 작성하지 않고, 컴파일이 가능 할 줄 알았는데,
타입스크립트에서도 여전히 데코레이터는 실험적 기능으로 취급받고 있는 것을 처음 알게 되었다..
즉, tsconfig.json
파일을 만들어야 한다는 것이다.
데코레이터를 사용하기 위한 3 가지 옵션이 있다.
target
: ES5 or ES6module
: CommonJS or ESNextexperimentalDecorators
: true
experimentalDecorators
옵션을 왜 추가해야 하나? 조사를 해 보니,
Typescript 에서도 실험적으로 추가 한 내용이라, 옵션을 따로 붙여줘야 한다고 한다.
그리고 ES5
, ES6
의 차이점은,
ES5
: 클래스 선언 시 클래스는 function 으로 파싱한다.ES6
: 클래스 선언 시, 클래스의 원형을 유지하며, ES5 보다 실행이 빠르다.
이러한 차이점이 있다.
여기서는 ES5
를 사용 할 건데, 이유는 자바스크립트의 원시적 코드를 보며,
클래스 자체와 프로토타입 간의 관계를 이해하기 위함이다.
tsconfig.json
{
"compilerOptions": {
"target": "ES5",
"module": "CommonJS",
"strict" : true,
"experimentalDecorators": true
},
"include": [
"./**/*.ts"
]
}
DI 를 위해 데코레이터의 메타데이터도 얻고자 한다면, emitDecoratorMetadata : true
를 추가하면 된다.
이후, reflect-metadata
외부 라이브러리를 npm 으로 설치하면 된다.
이제 위에서 사용했던 TypeScript 코드를 다시 실행 해 본다면,
➜ test-code ts-node decorator-ts.ts
데코레이터 타겟의 이름 : MyClass
MyClass 인스턴스 생성됨.
이제서야 데코레이터를 인식하는 것을 볼 수 있다.
tsconfig.json 파일을 생성하기 싫다면?
$ tsc --target ES5 --experimentalDecorators <컴파일 할 코드.ts>
$ node <컴파일 된 코드.js>
Result :
➜ tempTs tree
.
├── deco-test.js # es5 로 컴파일 된 JS 파일
└── deco-test.ts # 데코레이터를 적용한 TS 파일
이러한 방식으로 테스트 할 수 있다.
BUT!
메서드 데코레이터 작성 중,
@methodDeco
에서는 문제가 일어나지 않았지만,
@methodDeco()
에서는 문제가 일어났다.
이것이 왜 일어났는지 확인했는데,
타입스크립트가
- 메서드 데코레이터
- 프로퍼티 데코레이터
이 둘 중 프로퍼티 데코레이터 로 인식했기 때문이었다.
메서드 데코레이터는 기본적으로 3 개의 인자를 필요로 하는데,
메서드 자체를 프로퍼티로 인식하는 과정에서 2 개의 인자밖에 들어오지 않았다고 에러가 났다.
이를 해결하기 위해서는,
tsconfig.json
:
{
"compilerOptions": {
"target": "ES5",
"module": "commonjs",
"strict": true,
"experimentalDecorators": true,
},
"include": [
"./**/*.ts"
]
}
위의 설정 파일을 앞으로 코드를 작성하게 될 디렉토리에 저장하고,
# tsconfig.json 과 같은 디렉토리거나, 하위 디렉토리에서 실행하면 자동 인식.
$ tsc
난 명령줄을 좋아해서 명령줄로 어떻게 해 볼려고 했는데,
위의 옵션들을 한 번 실행 할 때 마다, 저 옵션들을 전부 명령어로 넣어줘야 해서,
이 방식으로 전부 컴파일하기로 했다.
참고로
# 이렇게 하면 안됨. - 데코레이터를 사용하므로 and tsconfig.json 을 참조하지 않음.
$ tsc xxxx.ts
라고 한다면, 해당 xxxx.ts
파일은 어떠한 tsconfig.json
도 참조하지 않고,
기본 옵션으로 실행되므로, 오류가 날 것이다.
클래스 데코레이터
먼저, 위에서 선언했던 코드를 더 살펴보자 :
function testDeco(target: any) {
console.log("데코레이터 타겟의 이름 :", target.name);
}
@testDeco
class MyClass {
constructor() {
console.log("MyClass 인스턴스 생성됨.");
}
}
const instance = new MyClass();
Run and Result :
데코레이터 타겟의 이름 : MyClass
MyClass 인스턴스 생성됨.
target 은 무엇을 의미하는가?
위의 testDeco
는 클래스를 목적으로 하고 있다.
따라서 target
은 클래스를 의미하는 줄 알았는데,
클래스의 constructor
를 의미했다.
그런데, 자바스크립트에서 클래스와 생성자는 "동일한 의미" 로 취급한다고 한다.
그렇다면, target
은 정확히 무엇을 의미하는가?
바로 target == MyClass
인 것이다.
target
은 "클래스" 이자, "생성자" 이다.
이게 무슨 뜻인가?
나 또한, C++, Java 로 클래스를 배워서 당혹스러웠다.
어떻게 "클래스" 와 "생성자" 가 동일한 주소, 즉, 객체를 의미하는가?
이는, Javascript 의 객체가 결국 Function
으로 구성되기 때문이다.
Example
TypeScript
파일 :
class FunctionClass {
title : string;
static staticalMember : number = 100;
constructor() {
this.title = "클래스가 어떻게 함수가 되는거지?"
}
}
const instance = new FunctionClass();
컴파일 된 JavaScript
파일 :
var FunctionClass = /** @class */ (function () {
function FunctionClass() {
this.title = "클래스가 어떻게 함수가 되는거지?";
}
FunctionClass.staticalMember = 100;
return FunctionClass;
}());
var instance = new FunctionClass();
위의 컴파일 된 코드를 본다면, 그 특징을 이해 할 수 있다.
말하자면, target
은 var FunctionClass
인 것이다.
왜 괄호가 없는가?
우리가 보는 데코레이터의 모습은, @testDeco
가 아닌, @testDeco()
의 형식으로,
괄호가 붙어있었을 것이다.
위의 표현 형식은 클래스, 메서드, 변수에서 사용 가능한 방식이며,
해당 클래스, 메서드, 변수에 어떠한 외부 값을 넣을 필요 없이,
곧바로 target
으로 직행 할 수 있다.
하지만, NestJS 와 같은 프레임워크에서 사용되는 데코레이터들은 항상 괄호를 붙인다.
왜 그럴까?
데코레이터에 어떠한 변수도 없는 경우
function NoArgsDeco(target : any) {
target["a"] = 1; // 적용된 클래스의 정적 요소에 a 를 추가
}
@NoArgsDeco ()
class TestingClass {
constructor() {}
}
const instance = new TestingClass();
console.log((TestingClass as any).a); // Result : 1
데코레이터 괄호에 변수가 들어가는 경우
function NoArgsDeco(newNum : number) : ClassDecorator {
return function (target: Function) {
// 객체의 "정적" 영역에 "a" 추가
Object.defineProperty(target, "a", {
value : newNum,
writable : true, // 속성 값 수정 가능 여부
configurable : true, // 속상 삭제, 수정 가능 여부
enumerable : true // 속성 열거 가능 여부 (숨길건지)
});
// "생성된 인스턴스" 혹은 "인스턴스의 (__proto__)" 에서만 접근 가능한 값 - 클래스에서는 불가.
Object.defineProperty(target.prototype, "thisProperty", {
value : "ThisProperty",
writable : true,
configurable : true,
enumerable : true,
});
}
}
@NoArgsDeco(123)
class TestingClass {
// TS 특성상 직접 선언해야 인식이 되므로, 직접 선언
static a : number;
thisProperty : string;
constructor() {}
}
const instance = new TestingClass();
console.log(TestingClass.a);
console.log(instance.thisProperty);
Result :
$ tsc
$ node <컴파일된 파일>.js
123
ThisProperty
보는 것 처럼, 데코레이터에 원하는 데이터를 입력하여,
클래스에 원하는 변수, 객체, 함수를 넣을 수 있다.
단, 기존의 function deco(target : Function) : void { ... }
가 아니라,
function TestDeco() : ClassDecorator {
return function (target : Function) {
// 객체에 할당하려던 로직, 공통 객체를 할당
}
}
한 번 더 감싸진다는 차이점이 존재한다.
솔직히 나는 마지막 방식이 좀 더 선호되는 것 같은데,
앞으로 해당 데코레이터를 사용하는 클래스들에 대해서 어떠한 기능을 넣을지도 모르고,
아직 외부 변수를 데코레이터 괄호를 통해 받을 생각이 없다면, @TestDeco()
를 통해
일관성을 지키는 것이 더 낫다고 생각된다.
메서드 데코레이터
메서드 데코레이터는, 메서드를 선언 직전에 선언된다.
예를 들어서, 우리가 NestJS 에서 사용했던 코드를 예시로 들자면,
@HttpCode(201)
@Get("api/v1/hello-world")
returnHello () {
return "Hello World!";
}
위의 코드는 NestJS
에서, 응답 코드를 201 로 변환 한 예시이다.
@Get()
애너테이션으로 작성된 메서드는 디폴트로 200 응답 코드를 반환하지만,
@HttpCode(201)
은, 응답 코드를 201 로 커스텀화 해 준다.
그리고, @Get(....)
내부의 문자열 경로를 API 경로로 등록시켜 준다.
메서드 데코레이터의 일반적인 형태란?
function methodDeco() : MethodDecorator {
return function (target : any, propertyKey, descriptor : PropertyDescriptor) {
/*
target ==> 이 데코레이터가 적용된 클래스에서 생성되는 인스턴스의 공통 접근. 그러나, 정적 메서드의 경우, "클래스 자체" 가 전달된다.
propertyKey ==> 해당 인스턴스에 할당된 프로퍼티 키
descriptor ==> 이 키에 할당된 실제 값(객체, 함수, 변수, 등등...),
*/
/*
descriptor 의 4 가지 속성
1. value : 키에 할당된 실제 값 (리터럴 값, 오브젝트, 함수, 클래스, 등등...
2. writable : 메서드를 덮어 쓸 수 있는가? (true or false)
3. configurable : 메서드 속성을 수정하거나 삭제 할 수 있는가? (true or false)
4. enumerable : 객체에서 key 들을 뽑거나, 혹은 탐색 시 보여 줄 수 있는가? (메서드 열거가 가능한가?) (true or false)
*/
}
}
Example :
function MethodDeco (recordName : string) : MethodDecorator {
return function (target : Object, propertyKey : string | symbol, descriptor : PropertyDescriptor) {
Object.defineProperty(target, "record", {
value : recordName
});
console.log("적용된 함수의 이름 : " + (propertyKey as string));
descriptor.value = function () {
console.log("함수가 바뀌었습니다.")
}
};
}
class MethodClass {
@MethodDeco("메서드 데코레이터에 들어간 문자열 변수.")
methodTest () {
console.log("asdf");
return "asdf";
}
}
const instance = new MethodClass();
console.log("인스턴스에서 바로 참조 : " + (instance as any).record);
console.log("인스턴스의 프로토타입에서 참조 : " + Object.getPrototypeOf(instance).record);
instance.methodTest();
Result :
$ tsc
$ node method-deco.js
적용된 함수의 이름 : methodTest
인스턴스에서 바로 참조 : 메서드 데코레이터에 들어간 문자열 변수.
인스턴스의 프로토타입에서 참조 : 메서드 데코레이터에 들어간 문자열 변수.
함수가 바뀌었습니다.
클래스 데코레이터와 달리, 메서드 데코레이터의 target
은,
인스턴스의 메서드일 경우 클래스의 prototype 을 의미하며,
(target
== MethodClass.prototype
and instance__proto__
)
클래스의 정적 메서드 일 경우, 클래스 자체 를 의미한다.
(target
== MethodClass
)
propertyKey
는 메서드의 이름(Key) 를 의미한다.
descriptor
는
value
: 메서드의 실제 함수writable
:true
일 경우, 속성 값 수정 가능,false
의 경우, 수정 불가.enumerable
:true
일 경우, 객체의 Key 로서 조회가 가능,false
는 그 반대.configurable
: 해당 속성의 재 정의 가능 여부.true
라면 삭제, 재정의 가능.false
는 그 반대.
descriptor.value = function () { ... }
로 재정의 할 수 있다.
만약 메서드의 성능을 측정하기 위해, 동작 시간을 검사하고자 한다면,
function MethodDeco (recordName : string) : MethodDecorator {
return function (target : Object, propertyKey : string | symbol, descriptor : PropertyDescriptor) {
let originMethod = descriptor.value;
descriptor.value = function(...args : any[]) : string {
// 현재 시각 기록
const startTime : number = Date.now();
console.log(`시작 시간 : ${ (new Date(startTime)).toLocaleString("ko-KR") }`);
// 기존 메서드의 실행 및 결과 저장
const result : string = originMethod.apply(this, args);
// 종료 시각 기록
const endTime : number = Date.now();
console.log(`종료 시간 : ${ (new Date(endTime)).toLocaleString() }`);
// 총 소요 시간 출력
console.log(`총 소요 시간 : ${endTime - startTime} ms`);
// 기존 메서드 실행 결과 반환
return result;
}
};
}
class MethodClass {
@MethodDeco("메서드 데코레이터에 들어간 문자열 변수.")
methodTest () {
return "반환값"
}
}
const instance = new MethodClass();
instance.methodTest();
Result :
$ node method-deco.js
시작 시간 : 2025. 3. 24. 오후 4:22:16
종료 시간 : 3/24/2025, 4:22:16 PM
총 소요 시간 : 30 ms
메서드의 이름은 string
혹은 symbol
타입이다.
그런데, symbol
은 +
연산자를 통해서도, 템플릿 문자열로도 출력할 수 없다.
따라서, 메서드의 이름도 같이 출력하고자 한다면,
let methodName;
if(typeof propertyKey === 'symbol') {
methodName = String(propertyKey)
} else {
methodName = propertyKey;
}
위의 코드를 데코레이터 반환 함수식에 넣어주면 된다.
접근자 데코레이터
클래스에는 get
, set
으로 정의할 수 있는 접근자가 존재한다.
Example :
class TestClass {
private _value;
constructor(value) {
this._value = value;
}
get _value() {
return this._value;
}
}
여기서, 접근자 데코레이터도 동일하게 메서드 데코레이터를 사용한다.
그런데, 공식문서의 예제가 빈약하여 스스로 예제를 만들었는데, 오류가 지속적으로 나오는 것이다.
Example typescript
:
function AccessorDeco(val : boolean) : MethodDecorator {
return function (target, propertyName, descriptor : PropertyDescriptor ){
descriptor.configurable = val;
let originalGet = descriptor.get;
descriptor.get = function () {
console.log("값을 가져감");
const result = originalGet?.apply(this);
return result;
}
}
}
class Pointer {
_x : number;
_y : number;
constructor(x : number, y : number) {
this._x = x;
this._y = y;
}
@AccessorDeco(false)
get x() {
return this._x;
}
@AccessorDeco(false)
get y() {
return this._y;
}
}
const pointerInstance = new Pointer(1, 2);
console.log(pointerInstance.x);
console.log(pointerInstance.y);
Result :
$ node accessor-deco.js
return c > 3 && r && Object.defineProperty(target, key, r), r;
^
TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>
at Function.defineProperty (<anonymous>)
at __decorate (/Users/gongdamhyeong/intelliJ/udemy/docs-to-learn/test-code/accessor-deco.js:6:33)
at /Users/gongdamhyeong/intelliJ/udemy/docs-to-learn/test-code/accessor-deco.js:38:5
at Object.<anonymous> (/Users/gongdamhyeong/intelliJ/udemy/docs-to-learn/test-code/accessor-deco.js:45:2)
at Module._compile (node:internal/modules/cjs/loader:1734:14)
at Object..js (node:internal/modules/cjs/loader:1899:10)
at Module.load (node:internal/modules/cjs/loader:1469:32)
at Function._load (node:internal/modules/cjs/loader:1286:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:235:24)
Node.js v23.10.0
접근자에 붙여놓은 데코레이터는, value
가 아닌, get
, set
으로 접근해야 한다는 내용이다.
에러 구문을 보면, 접근자에는 값이나 수정 가능한 속성을 동시에 지정 할 수 없다는 것이다.
이게 도대체 무슨 뜻일까? 곰곰히 생각해 봤다.
결과적으로, 메서드와 달리, 접근자는 애초에 "목적" 이 정해져 있다는 것이었다.
즉, get
, set
을 인지하는 순간,
descriptor
에서는 writable
무의미하다.. 즉, false 이다.
하지만, configurable
은 정할 수 있는데,
이는 프로퍼티 재정의, 삭제가 가능한지 결정 할 수 있는 것이다.
결과적으로 :
function AccessorDeco(val : boolean) : MethodDecorator {
return function (target, propertyName, descriptor : PropertyDescriptor ){
descriptor.configurable = val;
// get 가져오기
const originalGet = descriptor.get;
if(originalGet) {
descriptor.get = function () {
console.log("값을 가져감");
const result = originalGet.apply(this);
return result;
}
}
// set 가져오기
const originalSet = descriptor.set;
if(originalSet) {
descriptor.set = function (param : any) {
console.log("값을 설정함");
originalSet.apply(this, [param]);
}
}
}
}
class Pointer {
private _x : number;
private _y : number;
constructor(x : number, y : number) {
this._x = x;
this._y = y;
}
// get, set 마다 하나씩 데코레이터를 붙여야 하는 줄 알았는데, get + set = "x" 라는 동일 프로퍼티로, 하나의 접근자에만 데코레이터 적용을 한다.
@AccessorDeco(false)
get x() {
return this._x;
}
set x(x : number) {
this._x = x;
}
@AccessorDeco(false)
get y() {
return this._y;
}
set y(y : number) {
this._y = y;
}
}
const pointerInstance = new Pointer(1, 2);
// get x() 호출 --> 프로토타입에서
console.log(pointerInstance.x);
console.log(pointerInstance.y);
pointerInstance.x = 10;
pointerInstance.y = 20;
console.log(pointerInstance.x);
console.log(pointerInstance.y);
Result :
$ node accessor-deco.js
값을 가져감
1
값을 가져감
2
값을 설정함
값을 설정함
값을 가져감
10
값을 가져감
20
여기서 중요한 것은, 접근자 데코레이터를 붙일 때,
get
, set
을 따로 떼어놓아 데코레이터를 붙이는 것이 아니라,
get
, set
을 하나의 묶음으로 생각해야 한다는 것이다.
그리고 어떠한 데이터를 가져가거나 설정했을 때, 이를 로깅하는 용도로도 사용이 가능하다.
프로퍼티 데코레이터
프로퍼티 데코레이터 또한, 프로퍼티 선언 바로 전에 선언된다.
But, 공식문서에서
프로퍼티 데코레이터는 프로퍼티의 내용을 담고 있는 "설명자"(descriptor
) 가 인수로 제공되지 않는다.
이는, 현재 프로토타입의 멤버를 정의 할 때, 인스턴스 프로퍼티를 설명하는 매커니즘이 없으며,
프로퍼티의 이니셜라이저를 관찰하거나 수정 할 수 있는 방법이 없기 때문이다.
따라서, 반환 값도 무시된다.
이로 인해, 특정 이름의 프로퍼티가 클래스에 선언되었음을 관찰하는 데에만 사용할 수 있다고 한다.
즉, 클래스 내부의 프로퍼티에 데코레이터를 단다고 해서,
직접적으로 해당 프로퍼티에 접근할 수 없다는 것이다.
이러한 한계 때문에, 위의 공식문서 내용에서 말하는 한계가 나온 것이다.
그렇다면, 이 데코레이터는 어떤 용도에서 사용 할 수 있는가?
NPM 모듈 중, reflect-metadata
를 다운로드 받아 메타데이터로 사용할 수 있다고 한다.
만약, reflect-metadata 를 사용하고자 한다면,
{
"compilerOptions": {
"target": "ES5",
"module": "CommonJS",
"experimentalDecorators": true,
"strict" : true,
"emitDecoratorMetadata" : true
},
"include": [
"./**/*.ts"
]
}
위의 옵션 중 emitDecoratorMetadata
를 활성화시켜,
reflect-metadata
를 사용할 수 있게 만들어야 한다.
공식문서 예제 :
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);
}
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);
}
}
그렇다면, reflect-metadata
를 사용하지 않고, 파라미터를 데코레이팅 해 보겠다.
그런데, 여기서 tsconfig.json
의 strict
설정을 없애거나, false
로 설정하고 시작한다.
function inject(parameter : any) {
return function(target : any, propertyName : string) {
target[propertyName] = "asdf";
}
}
class Person {
@inject(200)
public height : number;
}
const person = new Person();
console.log(person.height);
Result :
$ node property-deco.js
asdf
이러한 방식으로, 직접 클래스의 변수에 값을 입력 해 줄 수도 있다.
그런데, 이 시점에서 Spring 에서 사용되는 세터(Setter), 게터(Getter) 가 생각났다.
따라서, 클래스의 인스턴스 변수의 접근자를 자동으로 설정하는 클래스를 생성 해 보았다.
function inject(parameter : any) {
return function(target : any, propertyName : string) {
target[propertyName] = parameter;
}
}
function getterAndSetter() {
return function(target : any, propertyName : string) {
let propName = propertyName.replace("_", "");
console.log("propName : " + propName);
Object.defineProperty(target, propName, {
get(): any {
return this[propertyName];
},
set(v: any) {
this[propertyName] = v;
}
})
}
}
class Person {
@inject(200)
@getterAndSetter()
private _height : number;
}
const person = new Person();
console.log(person["height"]);
Result :
$ node property-deco.js
propName : height
200
클래스 내부에서 private
으로 설정된 변수를 직접 가져 올 수는 없다.
따라서, 이를 접근하고 변경 할 수 있는 데코레이터를 만들어, 접근 혹은 변경을 가능하게 만들었다.
매개변수 데코레이터
이는 매개 변수 선언 직전에 선언된다.
매개변수 데코레이터는 "클래스 생성자" 혹은 "메서드 선언 함수" 에 적용된다.
매개 변수 데코레이터는 세 개의 인수로 호출된다.
- 정적 멤버에 대한 클래스의 생성자 함수 또는 인스턴스 멤버에 대한 클래스의 프로토타입
- 멤버의 이름
- 함수의 매개 변수 목록에 있는 매개 변수의 서수 색인(ordinal index) - 몇 번째 인자 인지.
function parameterDeco(): ParameterDecorator {
return function (target, propertyKey, parameterIndex) {
console.log(target);
console.log(propertyKey);
console.log(parameterIndex);
target[propertyKey]("데코레이터 내부에서 함수 불러보기 ");
}
}
class ParamClass {
constructor() {
}
printing(@parameterDeco() word : string) {
console.log(word);
}
}
const paramInstance = new ParamClass();
paramInstance.printing("damsoon");
$ node parameter-deco.js
{ printing: [Function (anonymous)] }
printing
0
데코레이터 내부에서 함수 불러보기
damsoon
파라미터 데코레이터는 주로 "메서드 데코레이터" 와 같이 사용된다.
참고 사이트
https://www.typescriptlang.org/ko/docs/handbook/decorators.html
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes