Skip to content

Latest commit

 

History

History
436 lines (302 loc) · 24.3 KB

File metadata and controls

436 lines (302 loc) · 24.3 KB

Item 51 ~ 53

[아이템51] 의존성 분리를 위해 미러 타입 사용하기

CSV 파일을 파싱하는 라이브러리를 작성한다고 가정해 볼 때, parseCSV API는 CSV 파일의 내용을 매개변수로 받고, 열 이름을 값으로 매핑하는 객체들을 생성하여 배열로 반환한다.

그리고, nodeJS 사용자를 위해 매개변수에 Buffer 타입을 허용한다.

function parseCSV(contents: string | Buffer): {[column: string]: string}[] {
  if (typeof contents === 'object' {
		// buffer인 경우
		return parseCSV(contents.toString('utf-8');
	}
	...
}

이렇게 작성한 CSV 파싱 라이브러리를 공개하면 타입 선언도 포함하게 된다. 그리고, @types/node에 의존하기 때문에 @types/node는 devDependencies로 포함해야 한다.

그러나 @types/node를 devDependencies로 포함하면 다음 두 그룹의 라이브러리 사용자들에게 문제가 생긴다.

  • @types와 무관한 자바스크립트 개발자
  • nodeJS와 무관한 타입스크립트 개발자

두 그룹의 사용자들은 각자가 사용하지 않는 모듈이 포함되어 있기 때문에 혼란스러울 것이다. Buffer는 node.js 개발자만 필요하다. 그리고 @types/node는 nodeJS와 타입스크립트를 동시에 사용하는 개발자만 관련된다.

각자가 필요한 모듈만 사용할 수 있도록 구조적 타이핑을 적용할 수 있다.

@types/node에 있는 Buffer 선언을 사용하지 않고 필요한 메서드와 속성만 별도로 작성할 수 있다. 앞선 예제의 경우에는 인코딩 정보를 매개변수로 받는 toString 메서드를 가지는 인터페이스를 별도로 만들어 사용하면 된다.

interface CsvBuffer {
  toString(encoding: string): string;
}

function parseCSV(contents: string | CsvBuffer): {[column: string]: string} [] {
	...
}

CsvBuffer는 Buffer 인터페이스보다 훨씬 짧으면서도 실제로 필요한 부분만을 떼어 내어 명시했다. 또한 해당 타입이 Buffer와 호환되기 때문에 nodeJS 프로젝트에서는 실제 Buffer 인스턴스로 parseCSV 를 호출하는 것이 가능하다.

parseCSV(new Buffer('col1, col\nval1, val2', 'utf-8')); // 정상

만약 작성 중인 라이브러리가 의존하는 라이브러리의 구현과 무관하게 타입에만 의존한다면, 필요한 선언부만 추출하여 작성 중인 라이브러리에 넣는 것(미러링)을 고려해 보는 것도 좋다.

nodeJS 기반 타입스크립트 사용자에게는 변화가 없지만, 웹 기반이나 자바스크립트 등 다른 모든 사용자에게는 더 나은 사양을 제공할 수 있다.

다른 라이브러리의 타입이 아닌 구현에 의존하는 경우에도 동일한 기법을 적용할 수 있고 타입 의존성을 피할 수 있다. 그러나 프로젝트의 의존성이 다양해지고 필수 의존성이 추가됨에 따라 미러링 기법을 적용하기가 어려워진다.

다른 라이브러리의 타입 선언의 대부분을 추출해야 한다면, 차라리 명시적으로 @types 의존성을 추가하는 게 좋다.

미러링 기법은 유닛 테스트와 상용 시스템 간의 의존성을 분리하는 데도 유용하다.

요약

- 필수가 아닌 의존성을 분리할 때는 구조적 타이핑을 사용하자
- 공개한 라이브러리를 사용하는 자바스크립트 사용자가 @types 의존성을 가지지 않게 해야 한다.
- 그리고  개발자가 nodeJS 관련된 의존성을 가지지 않게 해야 한다.

[아이템 52] 테스팅 타입의 함정에 주의하기

프로젝트를 공개하려면 테스트 코드를 작성하는 것은 필수이며, 타입 선언도 테스트를 거쳐야 한다. 그러나 타입 선언을 테스트하기는 매우 어렵다.

그래서 타입 선언에 대한 테스트 코드를 작성할 때 타입스크립트가 제공하는 도구를 사용하여 단언문으로 때우기 십상이지만, 이런 방법에는 몇 가지 문제가 있다. 궁극적으로는 dtslint 또는 타입 시스템 외부에서 타입을 검사하는 유사한 도구를 사용하는 것이 더 안전하고 간단하다.

유틸리티 라이브러리에서 제공하는 map 함수의 타입 선언을 작성한다고 가정하자.

declare function map<U, V>(array: U[], fn: (u: U) ⇒ V): V[];

타입 선언이 예상한 타입으로 결과를 내는지 체크할 수 있는 한 가지 방법은 함수를 호출하는 테스트 파일을 작성하는 것이다.

map([’2017’, ‘2018’, ‘2019’], v ⇒ Number(v));

이 코드는 오류 체크를 수행하지만 허점이 존재한다. 예를 들어 map의 첫 번째 매개변수에 배열이 아닌 단일 값이 있었다면 매개변수의 타입에 대한 오류는 잡을 수 있다. 그러나 반환값에 대한 체크가 누락되어 있기 때문에 완전한 테스트라 볼 수 없다.

예를 들어, 앞의 코드와 동일한 스타일로 square라는 함수의 런타임 동작을 테스트한다면 다음과 같은 테스트 코드가 된다.

test('square a number', () => {
	square(1);
	square(2);
});

이 테스트 코드는 square 함수의 ‘실행'에서 오류가 발생하지 않는지만 체크한다.

그런데 반환값에 대해서는 체크하지 않기 때문에, 실제로는 실행의 결과에 대한 테스트는 하지 않는 게 된다. 따라서 square의 구현이 잘못되어 있더라도 이 테스트를 통과하게 된다.

타입 선언 파일을 테스팅할 때는 이 테스트 코드처럼 단순히 함수를 실행만하는 방식을 일반적으로 적용하게 되는데, 그 이유는 라이브러리 구현체의 기존 테스트 코드를 복사하면 간단히 만들 수 있기 때문이다.

함수를 실행만 하는 테스트 코드가 의미 없는 것은 아니지만, 실제로 반환 타입을 체크하는 것이 훨씬 좋은 테스트 코드이다.

반환값을 특정 타입의 변수에 할당하여 간단히 반환 타입을 체크할 수 있는 방법을 알아보자.

const lengths: number[] = map(['john', 'paul'], name => name.length);

이 코드는 일반적으로 불필요한 타입 선언에 해당한다. 그러나 테스트 코드 관점에서는 중요한 역할을 하고 있다.

number[] 타입 선언은 map 함수의 반환 타입이 number[]임을 보장한다. 실제로 DefinitelyTyped를 살펴보면, 테스팅을 위해 정확히 동일한 방식을 사용한 수많은 타입 선언을 볼 수 있다. 그러나 테스팅을 위해 할당을 사용하는 방법에는 두 가지 근본적인 문제가 있다.

첫번째, 불필요한 변수를 만들어야 한다.

반환값을 할당하는 변수는 샘플 코드처럼 쓰일 수도 있지만, 일부 린팅 규칙을 비활성해야 한다. 일반적인 해결책은 변수를 도입하는 대신 헬퍼 함수를 정의하는 것이다.

function assertType<T>(x: T){}

assertType<number[]>(map['john', 'paul'], name => name.length));

두 번째, 두 타입이 동일한지 체크하는 것이 아니라 할당 가능성을 체크하고 있다. 다음 예제처럼 잘 동작하는 경우도 있다.

const n = 12;
assertType<number>(n); // 정상

n 심벌을 조사해 보면, 타입이 실제로 숫자 리터럴 타입인 12임을 볼 수 있다. 12는 number의 서브타입이고 할당 가능성 체크를 통과한다.

그러나 객체의 타입을 체크하는 경우를 살펴보면 문제를 발견하게 된다.

const beatles = ['john', 'paul', 'george', 'ringo'];
assertType<{name: string}[]>(
	map(beatles, name => ({
		name,
		inYellowSubmarine: name === 'ringo'
}))); // 정상

map은 {name: string, inYellowSubmarine: boolean} 객체의 배열을 반환한다. 반환된 배열은 {name: string}[]에 할당 가능하지만, inYellowSubmarine 속성에 대한 부분이 체크되지 않는다.

상황에 따라 타입이 정확한지 체크할 수도 있고, 할당이 가능한지 체크할 수도 있다. 게다가 assertType에 함수를 넣어보면 이상한 결과가 나타난다.

const add = (a: number, b: number) => a + b;
assertType<(a: number, b: number) => number>(add); // 정상

const double = (x: number) => 2 * x;
assertType(a: nuber, b: number) => number>(double); // 정상?

double 함수의 체크가 성공하는 이유는, 타입스크립트의 함수는 매개변수가 더 적은 함수 타입에 할당 가능하기 때문이다.

const g: (x: string) => any = () => 12; // 정상

앞의 코드는 선언된 것보다 적은 매개변수를 가진 함수를 할당하는 것이 아무런 문제가 없다는 것을 보여 준다. 이러한 사례는 콜백함수에서 흔히 볼 수 있기 때문에, 타입스크립트에서는 이러한(선언보다 많은 수의 매개변수) 동작을 모델링하도록 설계되어 있다.

예를 들어, 로대시의 map 함수의 콜백은 세 가지 매개변수를 받는다.

map(array, (name, index, array) => { ... });

콜백 함수는 세 가지 매개변수 name, index, array 중에서 한두 개만 보통 사용한다. 매개변수 세 개를 모두 사용하는 경우는 매우 드물다.

만약 매개변수의 개수가 맞지 않는 경우를 타입 체크에서 허용하지 않으면, 매우 많은 곳의 자바스크립트 코드에서 콜백 함수의 타입과 관련된 오류들이 발생하게 된다.

그럼, 제대로 된 assertType 사용 방법은 무엇일까? 다음 코드처럼 Parameters와 ReturnType 제너럴 타입을 이용해 함수의 매개변수 타입과 반환 타입만 분리하여 테스트할 수 있다.

const double = (x: number) => 2 * x;
let p:Parameters<typeof double> = null!;
assertType<[number, number]>(p);
// ~ '[number]' 형식의 인수는 '[number, number'] 형식의 매개변수에 할당될 수 없다.

let r: ReturnType<typeof double> = null!;
assertType<number>(r);  // 정상

한편, this가 등장하는 콜백 함수의 경우는 또 다른 문제가 있다. map은 콜백 함수에서 this의 값을 사용할 때가 있으며 타입스크립트는 이러한 동작을 모델링할 수 있으므로 타입 선언에 반영해야 하며 테스트도 해야 한다.

앞서 등장했던 map에 대한 테스트는 모두 블랙박스 스타일이었다. map의 매개변수로 배열을 넣어 함수를 실행하고 반환 타입을 테스트했지만, 중간 단계의 세부 사항은 테스트하지 않았따. 세부 사항을 테스트하기 위해서 콜백 함수 내부에서 매개변수들의 타입과 this를 직접 체크해 보자.

const beatles = ['john', 'paul', 'george', 'ringo'];
assertType<number[]>(map(beatles, function(name, i, array) {
	// '(name: any, i: any, array: any) => any' 형식의 인수는 '(u: string) => any'형식의 매개변수에 할당될 수 없다.
	assertType<string>(name);
	assertType<number)(i);
	assertType<string[]>(array);
	assertType<string[]>(this); // this에는 암시적으로 any 형식이 포함된다.

	return name.length;
}));

이 코드는 map의 콜백 함수에서 몇 가지 문제가 발생했다. 한편 이번 예제의 콜백 함수는 화살표 함수가 아니기 때문에 this의 타입을 테스트할 수 있음을 주의하자.

다음 코드의 선언을 사용하면 타입 체크를 통과한다.

declare function map<U, V>(
	array: U[],
	fn: (this: U[], u: U, i: number, array: U[]) => V
): V[];

그러나 여전히 중요한 마지막 문제가 남아 있다. 다음 모듈 선언은 까다로운 테스트를 통과할 수 있는 완전한 타입 선언 파일이지만, 결과적으로 좋지 않은 설계가 된다.

declare module 'overbar';

이 선언은 전체 모듈에 any 타입을 할당한다. 따라서 테스트는 전부 통과하겠지만, 모든 타입 안전성을 포기하게 된다. 더 나쁜 점은, 해당 모듈에 속하는 모든 함수의 호출마다 암시적으로 any 타입을 반환하기 때문에 코드 전반에 걸쳐 타입 안전성을 지속적으로 무너뜨리게 된다는 것이다. noImplicitAny를 설정하더라도 타입 선언을 통해 여전히 any 타입이 생겨나게 된다.

타입 시스템 내에서 암시적 any 타입을 발견해 내는 것은 매우 어렵다. 이러한 어려움 때문에 타입 체커와 독립적으로 동작하는 도구를 사용해서 타입 선언을 테스트하는 방법이 권장된다.

DefinitelyTyped의 타입 선언을 위한 도구는 dtslint다. dtslint는 특별한 형태의 주석을 통해 동작한다. dtslint를 사용하면 beatles 관련 예제의 테스트를 다음처럼 작성할 수 있다.

const beatles = ['john', 'paul', 'george', 'ringo'];
map(beatles, function(
	name,  // $ExpectType string
	i,  // $ExpectType number
	array // $ExpectType string[]
){
	this // $ExpectType string[]
	return name.length;  // $ExpectType number[]
});

dtslint는 할당 가능성을 체크하는 대신 각 심벌의 타입을 추출하여 글자 자체가 같은지 비교한다. 이 비교 과정은 편집기에서 타입 선언을 눈으로 보고 확인하는 것과 같은데, dtslint는 이러한 과정을 자동화한다. 그러나 글자 자체가 같은지 비교하는 방식에는 단점이 있다.

number | string과 string | number는 값은 타입이지만 글자 자체로 보면 다르기 때문에 다른 타입으로 인식된다. string과 any를 비교할 때도 마찬가지인데, 두 타입은 서로 간에 할당이 가능하지만 글자 자체는 다르기 때문에 다른 타입으로 인식된다.

타입 선언을 테스트한다는 것은 어렵지만 반드시 해야 하는 작업이다. 앞에서 소개한 몇 가지 일반적인 기법의 문제점을 인지하고 문제점을 방지하기 위해 dtslint같은 도구를 사용하도록 하자.

요약

- 타입을 테스트할 때는 특히 함수 타입의 동일성(equality) 할당 가능성(assignability) 차이점을 알고 있어야 한다.

- 콜백이 있는 함수를 테스트할 , 콜백 매개변수의 추론된 타입을 체크해야 한다. 또한 this가 API의 일부분이라면 역시 테스트해야 한다.

- 타입 관련된 테스트에서 any를 주의해야 한다.  엄격한 테스트를 위해 dtslint 같은 도구를 사용하는 것이 좋다..

[아이템 53] 타입스트립트 기능보다는 ECMAScript 기능을 사용하기

타입스크립트가 태동하던 2010년경, 자바스크립트는 결함이 많고 개선해야 할 부분이 많은 언어였다. 그리고 클래스, 데코레이터, 모듈 시스템 같은 기능이 없어서 프레임워크나 트랜스파일러로 보완하는 것이 일반적인 모습이었다. 그렇기 때문에 타입스크립트도 초기 버전에는 독립적으로 개발한 클래스, 열거형(enum), 모듈 시스템을 포함시킬 수밖에 없었다.

시간이 흐르며 TC39(자바스크립트를 관장하는 표준 기구)는 부족했던 점들을 대부분 내장 기능으로 추가했다. 그러나 자바스크립트에 새로 추가된 기능은 타입스크립트 초기 버전에서 독립적으로 개발했던 기능과 호환성 문제를 발생시켰다. 그렇기에 타입스크립트 진영에서는 다음 전략 중 하나를 선택해야 했다.

한가지 전략은 타입스크립트 초기 버전의 형태를 유지하기 위해 자바스크립트 신규 기능을 변형해서 끼워 맞추는 것이다. 또 다른 전략은 자바스크립트의 신규 기능을 그대로 채택하고 타입스크립트 초기 버전과 호환성을 포기하는 것이다.

타입스크립트 팀은 대부분 두 번째 전략을 선택했다. 결국 TC39는 런타임 기능을 발전시키고, 타입스크립트 팀은 타입 기능만 발전시킨다는 명확한 원칙을 세우고 현재까지 지켜오고 있다.

그런데 이 원칙이 세워지기 전에 이미 사용되고 있던 몇 가지 기능이 있다. 이 기능들은 타입 공간(타입스크립트)과 값 공간(자바스크립트)의 경계를 혼란스럽게 만들기 때문에 사용하지 않는 것이 좋다. 여기서는 피해야 하는 기능을 몇 가지 살펴본다. 그리고 볼가피하게 이 기능을 사용하게 될 경우 어떤 점을 유의해야 호환성 문제를 일으키지 않는지 알아보자.

열거형(enum)

많은 언어에서 몇몇 값의 모음을 나타내기 위해 열거형을 사용한다. 타입스크립트에서도 열거형을 사용할 수 있다.

enum Flavor {
	VANILLA = 0,
	CHOCOLATE = 1,
	STRAWBERRY = 2,
}
let flavor = Flavor.CHOCOLATE // 타입이 Flavor

Flavor // 자동 완성 추천 : VANILLA, CHOCOLATE, STRAWBERRY
Flavor[0] // 값이 VANILLA

단순히 값을 나열하는 것보다 실수가 적고 명확하기 때문에 일반적으로 열거형을 사용하는 것이 좋다. 그러나 타입스크립트의 열거형은 몇 가지 문제가 있다. 타입스크립트의 열거형은 다음 목록처럼 상황에 따라 다르게 동작한다.

  • 숫자 열거형에 0, 1, 2 외의 다른 숫자가 할당되면 매우 위험하다. (이 방법은 원래 비트 플래그 구조를 표현하기 위해 설계되었다.)
  • 상수 열거형은 보통의 열거형과 달리 런타임에 완전히 제거된다. 앞의 예제를 const enum Flavor로 바꾸면, 컴파일러는 Flavor.CHOCOLATE을 0으로 바꿔 버린다. 이런 결과는 기대하지 않은 것이며 문자열 열거형과 숫자 열거형과 전혀 다른 동작이다.
  • preserveConstEnums 플래그를 설정한 상태의 상수 열거형은 보통의 열거형처럼 런타임 코드에 상수 열거형 정보를 유지한다.
  • 문자열 열거형은 런타임의 타입 안정성과 투명성을 제공한다. 그러나 타입스크립트의 다른 타입과 달리 구조적 타이핑이 아닌 명목적 타이핑을 사용한다.

타입스크립트의 일반적인 타입들이 할당 가능성을 체크하기 위해서 구조적 타이핑을 사용하는 반면, 문자열 열거형은 명목적 타이핑(nominally typing)을 사용한다.

enum Flavor {
	VANILLA = 0,
	CHOCOLATE = 1,
	STRAWBERRY = 2,
}

let flavor = Flavor.CHOCOLATE // 타입이 Flavor
		flavor = 'strawberry'; // Flavor 형식에 할당될 수 없다.

명목적 타이핑은 라이브러리를 공개할 때 필요하다. Flavor를 매개변수로 받는 함수를 가정해보자

function scoop(flavor: Flavor) {...}

Flavor는 런타임 시점에는 문자열이기 때문에, 자바스크립트에서 다음처럼 호출할 수 있다.

scoop('vanilla'); // 자바스크립트에서 정상

그러나 타입스크립트에서는 열거형을 임포트하고 문자열 대신 사용해야 한다.

scoop('vanilla'); // Flavor 형식의 매개변수에 할당될 수 없음

import { Flavor } from 'ice-cream';
scoop(Flavor.VANILLA); // 정상

이처럼 자바스크립트와 타입스크립트에서 동작이 다르기 때문에 문자열 열거형은 사용하지 않는 것이 좋다. 열거형 대신 리터럴 타입의 유니온을 사용하면 된다.

type Flavor = 'vanilla' | 'chocolate' | 'strawberry';

let flavor: Flavor = 'chocolate' // 정상
		flavor = 'mint chip' // Flavor 유형에 할당될 수 없음

리터럴 타입의 유니온은 열거형만큼 안전하며 자바스크립트와 호환되는 장점이 있다. 그리고 편집기에서 열거형처럼 자동완성 기능을 사용할 수 있다.

function scoop(flavor: Flavor) {
	if (flavor === 'v // 자동완성이 vnilla를 추천
}

매개변수 속성

일반적으로 클래스를 초기화할 때 속성을 할당하기 위해 생성자의 매개변수를 사용한다.

class Person {
	name: string
	constructor(name: string) {
		this.name = name;
	}
}

타입스크립트는 더 간결한 문법을 제공한다.

class Person {
	constructor(public name: string) {}
}

예제의 public name은 ‘매개변수 속성'이라고 불리며, 멤버 변수로 name을 선언한 이전 예제와 동일하게 동작한다. 그러나 매개변수 속성과 관련된 몇 가지 문제점이 존재한다.

  • 일반적으로 타입스크립트 컴파일은 타입 제거가 이루어지므로 코드가 줄어들지만, 매개변수 속성은 코드가 늘어나는 문법이다.
  • 매개변수 속성이 런타임에는 실제로 사용되지만, 타입스크립트 관점에서는 사용되지 않는 것처럼 보인다.
  • 매개변수 속성과 일반 속성을 섞어서 사용하면 클래스의 설계가 혼란스러워진다.
class Person {
	first: string;
	last: string;
	constructor(public name: string) {
		[this.first, this.last] = name.split(' ');
	}
}

Person 클래스에는 세 가지 속성(first, last, name)이 있지만, first와 last만 속성에 나열되어 있고 name은 매개변수 속성에 있어서 일관성이 없다.

클래스에 매개변수 속성만 존재한다면 클래스 대신 인터페이스로 만들고 객체 리터럴을 사용하는 것이 좋다. 구조적 타이핑 특성 때문에 다음 예제처럼 할당할 수 있다는 것을 주의해야 한다.

class Person {
	constructor(public name: string) {}
}
const p: Person = { name: 'Jed Barlet' }; // 정상

매개변수 속성을 사용하는 것이 좋은지에 대해서는 찬반 논란이 있다. 글쓴이는 매개변수 속성을 선호지 않지만, 어떤 이들은 코드양이 줄어 들어서 좋아하기도 한다. 매개변수 속성은 타입스크립트의 다른 패턴들과 이질적이고 초급자에게 생소한 문법이라는 것을 기억해야 한다. 또한 매개변수 속성과 일반 속성을 같이 사용하면 설계가 혼란스러워지기 때문에 한 가지만 사용하는 것이 좋다.

네임스페이스와 트리플 슬래시 임포트

ECMAScript 2015 이전에는 자바스크립트에 공식적인 모듈 시스템이 없었다. 그래서 각 환경마다 자신만의 방식으로 모듈 시스템을 마련했다. Node.js는 require와 module.exports를 사용한 반면, AMD는 define 함수와 콜백을 사용했다.

타입스크립트 역시 자체적으로 모듈 시스템을 구축했고, module 키워드와 ‘트리플 슬래시' 임포트를 사용했다. ECMAScript 2015가 공식적으로 모듈 시스템을 도입한 이후, 타입스크립트는 충돌을 피하기 위해 module과 같은 기능을 하는 namespace 키워드를 추가했다.

namespace foo {
	function bar() {}
}
/// <reference path='other.ts' />
foo.bar();

트리플 슬래시 임포트와 module 키워드는 호환성을 위해 남아 있을 뿐이며, 이제는 ECMAScript 2015 스타일의 모듈(import와 export)을 사용해야 한다.

데코레이터

데코레이터는 클래스, 메서드, 속성에 annotation을 붙이거나 기능을 추가하는 데 사용할 수 있다. 예를 들어, 클래스의 메서드가 호출될 때마다 로그를 남기려면 logged annotation을 정의할 수 있다.

class Greeter {
	greeting: string;
	constructor(message: string) {
		this.greeting = message;
	}
	@logged
	greet() {
		return 'Hello, ' + this.greeting;
	}
}

function logged(target: any, name: string, descriptor: PropertyDescriptor) {
	const fn = target[name];
	descriptor.value = function() {
		console.log(`Calling ${name}`);
		return fn.apply(this, arguments);
	};
}

console.log(new Greeter('Dave').greet());
// 출력
// Calling greet
// Hello, Dave

데코레이터는 처음에 앵귤러 프레임워크를 지원하기 위해 추가되었으며 tsconfig.json에 experimentalDecorators 속성을 설정하고 사용해야 한다. 현재까지도 표준화가 완료되지 않았기 때문에, 사용 중인 데코레이터가 비표준으로 바뀌거나 호환성이 깨질 가능성이 있다. 앵귤러를 사용하거나 annotation이 필요한 프레임워크를 사용하고 있는 게 아니라면, 데코레이터가 표준이 되기 전에는 타입스크립트에서 데코레이터를 사용하지 않는 게 좋다.

요약

  • 일반적으로 타입스크립트 코드에서 모든 타입 정보를 제거하면 자바스크립트가 되지만, 열거형, 매개변수 속성, 트리플 슬래시 임포트, 데코레이터는 타입 정보를 제거한다고 자바스크립트가 되지는 않는다.
  • 타입스크립트의 역활을 명확하게 하려면, 열거형, 매개변수 속성, 트리플 슬래시 임포트, 데코레이터는 사용하지 않는 것이 좋다.