studye / typescript

타입스크립트는 자바스크립트랑 다른 언어인가요?
7 stars 0 forks source link

[Chapter 4] Decorator metadata #21

Open laziel opened 7 years ago

laziel commented 7 years ago

타입스크립트 컴파일러는 "데코레이터 메타데이터"라는 실험적인 기능을 지원한다. 클래스 정의할 때 데코레이터에 정보를 전달하기 위해 생성된다.

예를 들어, 아래와 같은 타입스크립트 코드는

function paramDec(target: any, methodName: string, paramIndex: number) {}
class testA {
  print(@paramDec is: number, name: string) : number {
       return 1000;
  }
}

아래와 같은 자바스크립트로 변환된다.

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
function paramDec(target, methodName, paramIndex) { }
var testA = (function () {
    function testA() {
    }
    testA.prototype.print = function (is, name) {
        return 1000;
    };
    return testA;
}());
__decorate([
    __param(0, paramDec)
], testA.prototype, "print", null);

이 코드를, tsconfig.json 파일에 emitDecoratorMetadata: true 옵션을 주어 컴파일하면 아래와 같이 __decorate() 함수 호출시 추가적인 정보가 전달되는 것을 확인할 수 있다.

...
var testA = (function () {
    function testA() {
    }
    testA.prototype.print = function (is, name) {
        return 1000;
    };
    return testA;
}());
__decorate([
    __param(0, paramDec),
    __metadata('design:type', Function),
    __metadata('design:paramtypes', [Number, String]),
    __metadata('design:returntype', Number),   
], testA.prototype, "print", null);

데코레이터에서 추가적인 정보를 사용하기 위해 서드파티 라이브러리 reflect-metadata를 사용해야 한다. 이 라이브러리는 아직 ECMAScript 표준에 포함되지는 않았지만 데코레이터가 공식적으로 표준 규격에 포함될 때에는 이 확장들 또한 함께 포함되도록 제안될 것이다. #

타입스크립트 컴파일러에 의해 자동으로 생성되는 메타데이터를 런타임에 얻을 수 있는것은 유용하다.

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

@required 파라미터 데코레이터에서 메타데이터에 대상 파라미터를 필수 항목으로 마킹해놓고, @validate 메소드 데코레이터에서 이 메타데이터를 갖고 검사하는 예제.