타입스크립트의 특성 상 타입 체커를 통과한 코드를 포함한 모든 타입스크립트 코드는 결국 자바스크립트로 변환되어 실행됩니다. 이때 모든 인터페이스와 타입이 제거되기 때문에 코드가 예상과 맞지 않게 동작할 때가 있습니다. 타입스크립트 타입 대신 사용할 수 있는 별도의 방법을 이펙티브 타입스크립트에 실린 예제를 중심으로 알아봅니다.
상술했던 대로 타입스크립트 코드는 런타임에 모든 타입이 제거됩니다. 따라서 특정 상황의 경우 선언된 타입과 런타임 시 실제 타입이 일치하지 않을 수 있습니다. 예를 들어 타입 단언을 사용하는 경우, 외부 API를 이용하는 경우, 또는 구조적 타이핑에 기반해 타입 체커를 통과하는 경우 등이 있습니다. 이를 방지하기 위해 런타임에 타입 검사를 하는 일이 필요합니다.
interface Square {
width: number;
}
interface Rectangle extends Square {
height: number;
}
type Shape = Square | Rectangle;
function getArea(shape: Shape) {
// 'Rectangle' only refers to a type, but is being used as a value here.(2693)
if (shape instanceof Rectangle) {
// Property 'height' does not exist on type 'Shape'.
// Property 'height' does not exist on type 'Square'.(2339)
return shape.width * shape.height;
} else {
return shape.width * shape.width;
}
}
위의 코드는 타입스크립트에서 오류를 내뿜을 뿐더러 자바스크립트로 변환해 실행되도 제대로 타입 검사가 이루어지지 않습니다. 그 이유는 instanceof 연산자의 매개변수로 값이 아닌 타입스크립트의 타입을 전달하고 있기 때문입니다. 이를 해결하기 위해 책에서는 세가지 방법을 제시합니다.
in 연산자를 사용하면 객체가 특정 속성을 가지고 있는지 체크할 수 있습니다. 위에서 봤던 예제의 경우, height 속성의 보유 여부를 검사하면 Shape 객체가 Square인지 Rectangle인지 판별할 수 있게 됩니다. 따라서 getArea 함수를 다음과 같이 재작성할 수 있습니다.
function getArea(shape: Shape) {
if ("height" in shape) {
return shape.width * shape.height;
} else {
return shape.width * shape.width;
}
}
태그 기법이란, 런타임에 접근 가능한 타입 정보를 명시적으로 저장하는 것을 말합니다.
interface Square {
_type: "square";
width: number;
}
interface Rectangle {
_type: "rectangle";
width: number;
height: number;
}
...
function getArea(shape: Shape) {
if (shape._type === "rectangle") {
...
리터럴 타입을 사용해 타입 정보를 스트링 리터럴로 명시해 준 것을 볼 수 있습니다. 이 경우 Square 인터페이스를 그대로 상속받아 Rectangle 인터페이스를 선언하는 것은 불가능하며, 별개로 Rectangle 인터페이스를 선언하거나 Pick, Omit 등을 이용해 리터럴 속성을 제외한 속성만을 상속받아 타입을 구현할 수 있습니다.
타입을 클래스로 선언하면 해당 타입을 타입뿐만 아니라 값으로 이용할 수 있습니다. 따라서 런타임에 instanceof 연산자가 문제 없이 동작하게 됩니다.
class Square {
constructor(public width: number) {}
}
class Rectangle extends Square {
constructor(public width: number, public height: number) {
super(width);
}
}
...
function getArea(shape: Shape) {
if (shape instanceof Rectangle) {
...
그 동안 instanceof 연산자 또는 is 키워드(type predicate)만을 사용해 타입 검사를 해왔습니다. 그래서 런타입에 타입을 검사할 필요가 있을 때 이를 구현하는 방법이 막막했는데 새로운 인사이트를 얻을 수 있었습니다. 클래스를 활용하는 방법은 해당 클래스 객체를 이용할 일이 있을 때만 사용할 것 같고, 주로 in 연산자나 태그 기법을 활용할 것 같습니다.