Angular는 클래스 생성에 필요한 인자들을 어떻게 알까

By @mnkim5/8/2018angular

P4280164 (1).jpg

의존성 주입 패턴은 Angular의 핵심 시스템입니다. 이 패턴으로 각 모듈은 더 이상 다른 모듈을 직접 참조하거나 생성하지 않습니다. 필요한 모듈은 일반적으로 '컨테이너'라 불리는 별도의 인스턴스 관리 객체로 부터 주입받습니다.

이 의존성 주입 시스템은 사실 순수하게 Angular의 기능은 아닙니다. Javascript를 래핑해 타입 시스템을 추가한 언어인 Typescript와 proposal 단계의 ECMASscript 스펙 reflect-metadata가 이 패턴이 동작하는 데 필요한 역할들을 하고 있습니다.

의존성 주입 시스템의 필요한 조건들 중 하나는 어떤 클래스가 생성될 때 어떤 인자를 필요로 하는지 알고 있어야 한다는 것입니다. 이 글이 쓰여질 때 ECMAScript의 최신 버전인 2018 에도 아직 해당 스펙이 존재하거나 논의되지 않고 있습니다. 따라서 순수한 Javascript만으로는 별도의 처리 없이 이 패턴을 구현하기가 까다롭습니다. 따라서 Typescript와 같은 트랜스파일러가 꼭 필요합니다.

Typescript는 Decorator가 달린 클래스를 트랜스파일 할 때 이를 처리하기 위한 폴리필 함수를 소스에 추가합니다 1). 아래는 Typescript소스와 이를 트랜스파일한 소스입니다.

  1. 공식 문서에 따르면 Decorator와 Metadata를 사용하려면 tsconfig.json에 experimentalDecorators, emitDecoratorMetadata옵션을 true로 설정해야 합니다.
// User.ts
@log
class User {
  constructor(name: string) {}
}

// bundle.js
"use strict";
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 === "undefined" ? "undefined" : _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;
};

/********** [참고1] **********/
var __metadata = this && this.__metadata || function (k, v) {
    if ((typeof Reflect === "undefined" ? "undefined" : _typeof(Reflect)) === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};

Object.defineProperty(exports, "__esModule", { value: true });
var User = /** @class */function () {
  function User(name) {}
  User = __decorate([
    decorators_1.forClass,
    /********** [참고2] **********/
    __metadata("design:paramtypes", [String])
  ], User);
  return User;
}();

트랜스파일 된 소스엔 원본 파일에 있는 소스 말고도 __decorate__metadata라는 두 함수가 추가로 정의되어 있습니다.

__decorate함수는 @log의 기능을 User 클래스에 적용하기 위한 폴리필 함수입니다. 아래에서 User클래스를 생성할 때 log Decorator를 배열에 담아 인자로 호출하고 있습니다. Decorator가 달린 클래스들은 전부 동일하게 트랜스파일됩니다.

__metadata함수*[참고1]*는 이 글의 주제인 의존성 주입 패턴과 밀접한 관련이 있습니다. 강조된 코드에서 __metadata함수에 "design:paramtypes" 리터럴 문자열과 배열로 String 클래스를 넘기는 것을 볼 수 있습니다. [참고2] 이 정보가 바로 User클래스 생성자 함수의 인자에 대한 정보입니다.

이어서 __metadata함수는 전달받은 생성자 파라미터 정보를 그대로 Reflect.metadata함수의 인자로 호출하고 있습니다. 이렇게 메타데이터가 등록된 후 아래의 코드를 실행하면 앞서 언급했던 조건인 User클래스의 생성자가 어떤 인자를 필요로 하는지에 대한 정보를 얻을 수 있습니다.

Reflect.getMedata('design':paramtypes', User);
// log: [String]

reflect-metadata API는 TC39에 아직 등록조자 되지 않은 Experimental 상태의 비표준 API입니다. Typescript공식 문서에서는 Decorator가 표준이 될 때 아마 같이 표준으로 등록될 것이라고 안내하고 있습니다.

실제로 `ReflectionCapabilities`클래스 내부 구현을 보면 Angular가 해당 API를 사용하고 있는 것을 볼 수 있습니다. reflect-metadata말고도 direct API, tsickle등의 도구에서 분석된 파라미터도 참고하고 있는 것을 볼 수 있습니다만. 기본적인 방식은 같습니다.

참고

3

comments