코드 생성과 타입이 관계없음을 이해하기
큰 그림에서, 타입스크립트 컴파일러는 다음의 두 가지 역할을 수행합니다.
- 최신 TS/JS를 브라우저에서 동작할 수 있도록 구버전의 JS로 트랜스파일합니다.
- 코드의 타입 에러를 체크합니다.
여기서 놀라운 점은, 위의 두가지는 완벽히 별개의 일이라는 겁니다. 즉 어느 한쪽이 제대로 이루어지지 않더라도 다른 한쪽이 이루어지는데는 문제가 없습니다.
타입 에러가 있어도 컴파일이 가능합니다
타입 체크와 컴파일이 동시에 이루어지는 자바나 C 같은 언어에서는 이것이 굉장히 황당할 겁니다. TS에서의 타입 에러는 C나 자바에서의 경고(WARNING)에 가깝습니다. 즉, 문제가 될 부분을 알려주지만, 그렇다고 해서 빌드하는 것을 멈추지는 않습니다.
이런 부분 떄문에, 얼핏 TS가 엉성한 언어처럼 보일 수 있지만, 오히려 이런 특징은 도움이 됩니다. TS는 타입 에러가 발생하더라도 여전히 컴파일링을 진행할 수 있기 때문에, 해당 부분 외의 애플리케이션은 여전히 테스트할 수 있는 상태가 됩니다.
만약, 에러가 발생했을 때 컴파일을 진행하지 않고자 한다면, noEmitOnError
를 설정해주면 됩니다.
런타임에는 타입 체크가 불가능합니다
자바스크립트로 컴파일되는 과정을 거치게 되면, 그 과정에서 모든 인터페이스, 타입, 그 외의 타입 구문들은 모두 제거됩니다.
만약 런타임 시에도 타입 정보를 유지하고자 한다면 몇 가지 방법이 있습니다.
1. "태그" 기법
interface Square {
kind: 'square';
width: number;
}
interface Rectangle {
kind: 'rectangle';
width: number;
height: number;
}
type Shape = Square | Rectangle;
위와 같이 인터페이스에 kind
값을 지정해서 런타임에서도 타입 정보를 손쉽게 유지할 수 있습니다. 런타임 시에 객체의 kind
가 어떤 값인지를 체크하는 방식으로 이를 활용할 수 있죠. 이는 타입스크립트에서 실제로 흔하게 볼 수 있는 기법입니다.
2. 클래스
class Square {
constructor(public width: number) {}
}
class Rectangle extends Square {
constructor(public width: number, public height: number) {
super(width);
}
}
type Shape = Square | Rectangle
클래스를 사용한다면 타입(런타임 접근 불가)와 값(런타임 접근 가능)을 동시에 사용할 수 있습니다. 위와 같이 선언된 클래스는 타입과 값 모두로 사용할 수 있게되므로 shape instanceof Rectangle
과 같은 형태로 런타임 타입 체크를 할 수 있습니다.
타입 연산은 런타임에 영향을 주지 않습니다
value as number
와 같은 타입 연산은 실제로 컴파일된 이후의 런타임에는 아무런 역할도 하지 않습니다. 단순히 타입 체커에게 해당 value
를 어떤 타입으로 고려하라고 알려줄 뿐입니다.
런타임 타입은 선언된 타입과 다를 수 있습니다
타입스크립트에서는 런타임 타입과 선언된 타입이 매치되지 않는 상황이 생길 수도 있습니다. 이러한 상황은 가능한 피하는게 좋지만요.
타입스크립트 타입으로는 함수를 오버로드할 수 없습니다
타입스크립트에도 함수 오버로딩 기능이 있긴 하지만, 그것은 온전히 타입 수준에서 동작하는 것입니다. 실제로 아래의 예시는 오직 하나의 함수만을 생성하죠.
function add(a: number, b: number): number;
function add(a: string, b: string): string;
타입스크립트 타입은 런타임 성능에 영향을 주지 않습니다
여러 타입 구문은 JS로 변환되면서 전부 제거되기 떄문에, 실제 런타임의 성능에는 아무런 영향도 주지 않습니다. 따라서 타입스크립트의 정적 타입은 비용이 전혀 들지 않죠.
대신, 타입스크립트 컴파일러는 "빌드타임" 오버헤드가 있습니다. 다만 기본적으로 TS 컴파일러는 상당히 빠른 편이며, 특히 증분(Incremental) 빌드 시에 더욱 두드러집니다. 너무 오버헤드가 커진다면, 빌드 도구에서 트랜스파일만 진행(transpile only)하도록 설정하여 타입 체크를 건너뛸 수 있습니다.
타입스크립트가 컴파일하는 코드는 호환성을 높이고 성능 오버헤드를 감안할지, 아니면 호환성을 포기하고 성능 중심의 네이티브 구현체를 선택할지의 문제에 맞닥뜨릴 수도 있습니다. 어떤 경우든지 이러한 호환성과 성능 간의 선택은 컴파일 타깃과 언어 레벨의 문제이며, 여전히 타입과는 전혀 무관합니다.