이펙티브 타입스크립트 (댄 밴더캄 지음) 를 읽고 정리
📍타입 에러가 있는 코드도 컴파일이 가능
- 타입 체크와 컴파일은 독립적으로 동작
- 문제가 있는 부분을 알려주지만, 빌드가 중단되지는 않음
- tsconfing.json에서 noEmitOnError를 설정하면 타입 에러일 때 컴파일 X
📍런타임에는 타입 체크 불가
- 런타임에서는 타입을 활용할 수 없다
아래 코드에서 그 예시를 확인할 수 있다
interface Square {
width: number;
}
interface Rectangle extends Square {
height: number;
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if (shape instanceof Rectangle) { // 에러 : Rectangle 타입이 값으로 사용됨
return shape.width * shape.height; // 에러 : Shape 타입에 height가 없음
} else {
return shape.width * shape.width;
}
}
- 타입스크립트의 타입은 제거 가능하다 (자바스크립트로 컴파일 되는 과정에서 실제로 제거됨)
위 코드의 에러를 해결하는 방법 (런타임에 타입을 지정하는 방법) 3가지가 있다
1️⃣런타임에서 프로퍼티(값) 체크
타입 체커가 shape 타입을 Rectangle로 보정해줌
interface Square {
width: number;
}
// Rectangle 타입은 Square 타입을 확장
interface Rectangle extends Square {
// width: number 도 필요
height: number;
}
const v: Rectangle = { // width 프로퍼티 없어서 에러 발생
height: 2
}
// 런타임에 타입 정보를 유지할 수 있음
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if ("height" in shape) { // 런타임에서 shape를 Rectangle로 자동 보정
return shape.width * shape.height;
} else {
return shape.width * shape.width;
}
}
2️⃣tagged union 으로 런타임에서 타입 정보를 유지
런타임에서 접근 가능한, 명시적인 타입 정보 프로퍼티를 사용해서 타입 정보 유지
interface Square {
kind: "square",
width: number;
}
interface Rectangle {
kind: "rectangle",
width: number,
height: number;
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if (shape.kind === "rectangle") {
return shape.width * shape.height;
} else {
return shape.width * shape.width;
}
}
위의 두 타입을 클래스로 만들 수도 있다.
3️⃣클래스를 사용하면, 타입과 값을 둘 다 사용할 수 있다
class Square {
constructor(public width: number) {}
}
class Rectangle extends Square {
constructor(public width: number, public height: number) {
super(width);
}
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
// shape가 Rectangle 타입임
if (shape instanceof Rectangle) { // 런타임에서 shape를 Rectangle로 자동 보정
return shape.width * shape.height;
} else {
return shape.width * shape.width;
}
}
📍타입 연산은 런타임에 영향을 주지 않는다
타입 체커를 통과하지만 잘못된 방법을 쓴 코드
// string or number 타입인 값을 항상 number로 바꾸는 함수
function asNumber(val: number | string): number {
return val as number; // 타입 단언문 (assertion)
}
// 컴파일된 자바스크립트 코드
function asNumber(val) {
// 어떠한 정제 과정도 없음
return val;
}
이렇게 바꿔야 한다
// string or number 타입인 값을 항상 number로 바꾸는 함수
function asNumber(val: number | string): number {
return typeof(val) === "string" ? Number(val) : val;
}
// 컴파일된 자바스크립트 코드
function asNumber(val) {
return typeof (val) === "string" ? Number(val) : val;
}
📍타입스크립트 타입으로는 함수 오버로딩이 불가능
- 함수 오버로딩 : 동일한 이름에 매개변수만 다른 여러 버전의 함수를 허용하는 것
타입스크립트에서는 타입과 런타임의 동작이 무관하기 때문에 함수 오버로딩 불가
// 중복된 함수 구현으로 에러 발생
function add(a: number, b: number) {
return a + b;
}
// 중복된 함수 구현으로 에러 발생
function add(a: string, b: string) {
return a + b;
}
- 단지 타입 수준의 함수 오버로딩만 가능
하나의 함수에 대해 여러 선언문(콜 시그니처)을 작성할 수 있지만, 구현체는 단 하나
// 여러 콜 시그니처를 만들 수 있음
function add(a: number, b: number): number;
function add(a: string, b: string): string;
📍타입스크립트 타입은 런타임 성능에 영향을 주지 않음
- 타입과 타입 연산자는 컴파일 시점에 제거되므로, 런타임 성능에 영향 X
=> 런타임 오버헤드가 없는 대신 (컴파일로 인한)빌드타임 오버헤드는 있다
- 가령, 이러한 경우가 있을 수 있음
제네레이터 함수가 ES5 타겟으로 컴파일되려면, 타입스크립트 컴파일러는 호환성을 위한 헬퍼 코드를 추가해야 함
- 호환성을 높이기 위해 성능 오버헤드를 감수할지? (헬퍼 코드 작성으로 인한)
- 성능 중심의 네이티브 구현체(ES6+ 스펙)를 선택할지? (트랜스파일링 X)
=> 이는 타입과 무관하다!!