타입스크립트는 자바스크립트의 상위집합(superset)이다.
타입 시스템은 런타임 오류를 일으키는 코드를 미리 찾고자 한다.
const states = [
{ name: 'Alabama', capital: 'Montogmery' },
{ name: 'Alaska', capital: 'Juneau' },
{ name: 'Arizona', capital: 'Phoenix' },
// ...
];
for (const state of states) {
console.log(state.capitol); // JS에서는 undefined, TS에서는 에러
}
interface State {
name: string;
capital: string;
}
const states: State[] = [
{ name: 'Alabama', capitol: 'Montogery' },
{ name: 'Alaska', capitol: 'Juneau' },
{ name: 'Arizona', capitol: 'Phoenix' },
];
// 🚨 Error
// 'State' 형식에 'capitol'이 없습니다.
// 'capital'을(를) 쓰려고 했습니까?
for (const state of states) {
console.log(state.capital);
}
타입스크립트 설정 파일인 tsconfig.json
에서 100여가지의 타입 체커 설정을 할 수 있다.
아래의 코드는 타입스크립트 설정에 따라서 타입 체커를 통과할 수도, 통과하지 못할 수도 있다.
function add(a, b) { // 다음과 같은 타입으로 추론된다: function add(a: any, b: any): any
return a + b;
}
add(10, null);
타입스크립트 설정 중 noImplicitAny
이란 설정이 있다. 이 설정은 타입이 명시적으로 선언되지 않았는데 추론된 타입이 any
이면 오류를 일으킨다. 따라서 이 설정이 켜져있다면, 타입스크립트는 위의 add
함수에 오류를 일으킨다.
strictNullChecks
이라는 설정은 모든 타입에 기본으로 포함되어 있는 null과 undefined를 제거해준다.
// strictNullChecks 해제 시
const x: number = null; // 정상
// strictNullChecks 설정 시
const x: number = null; // 🚨 에러: 'null' 형식은 'number' 형식에 할당할 수 없습니다.
다른 사람들이 작성해둔 타입스크립트 프로젝트 설정을 별 생각없이 따라하기 쉽다. 그러다 보면, 각 설정들이 정확히 어떤 것인지도 파악하기 어렵고, 어떤 설정들을 활용할 수 있는지도 자세히 알지 못한채 지나치기 쉽다. 모든 설정을 다 알 수야 없겠지만, 여러 프로젝트들의 tsconfig.json
을 보면서 자주 보이는 설정들은 자세히 알아두어야겠다.
이 두 가지는 서로 독립적이다. 타입 오류가 있더라도 트랜스파일은 가능하다. 즉, 타입 오류가 있어도 실행이 가능하다.
런타임에는 타입 체크가 불가능하다.
자바스크립트로 트랜스파일되는 과정에서 모든 인터페이스, 타입, 타입 구문은 그냥 제거되어 버린다.
런타임에 타입 정보를 유지하는 방법
interface Squrre {
kind: 'square';
width: number;
}
interface Rectangle {
kind: 'rectangle';
height: number;
width: number;
}
type Shape = Square | Rectangle; // '태그된 유니온(tagged union)'
function calculateArea(shape: Shape) {
if (shape.kind === 'rectangle') {
shape; // 타입이 Rectangle
return shape.width * shape.height;
} else {
shape; // 타입이 Square
return shape.width * shape.width;
}
}
타입 연산은 런타임에 영향을 주지 않는다.
런타임 오버헤드가 없는데신 타입스크립트 컴파일러는 빌드타임 오버헤드가 있다.
🐤 구조적 타이핑 (덕 타이핑, duck typing)
객체가 어떤 타입에 부합하는 변수와 메서드를 가질 경우 객체를 해당 타입에 속하는 것으로 간주하는 방식
자바스크립트는 본질적으로 덕 타이핑(duck typing) 기반이다.
다음의 예제에서 coord
는 Vector3D의 속성을 가지고 있음에도 calculateLength
의 파라미터로 넣을 수 있다.
interface Vector2D {
x: number;
y: number;
}
function calculateLength(v: Vector2D) {
return Math.sqrt(v.x * v.x + v.y * v.y);
}
interface VectorAndCity {
x: number;
y: number;
city: string;
}
const coord = { x: 1, y:2, city: 'seoul'}
calculateLength(coord) // 정상
타입스크립트는 매개변수 v를 구조적 타이핑 관점으로 다룬다. 즉 coord
가 x와 y 속성을 갖기 때문에 calculateLength
로 호출이 가능하다.
구조적 타이핑이 문제가 되는 경우가 있다.
function getVector3D (v: Vector3D) {
for( const axis of Object.keys(v)) {
const value = v[axis];
// 에러: 'string'은 'Vector3D'의 인덱스로 사용할 수 없기에
// 엘리먼트는 암시적으로 'any' 타입입니다.
}
}
매개변수 v 는 구조적 타이핑에서 열려있기 때문에 다음과 같은 변수가 삽입될 수도 있다.
{ x: 3, y: 5, z:4, address: "Seoul"}
추가적인 키 값에 어떤 타입이 매핑될지를 알 수 없기에 타입스크립트는 v[axis]
가 어떤 타입으로 될지 모르는 것이다.
타입스크립트의 타입 시스템은 any
타입이나 타입 단언문 as any
로 무력화시킬 수 있다.
따라서 any
는 타입스크립트의 수많은 장점을 누리지 못하게 막는다.
any 타입에는 타입 안정성이 없다
let age: number;
age = '12' as any; // 정상
age += 1; // 런타임에 정상, 🚨 age는 '121'
any는 함수 시그니처를 무시해 버린다
function calculateAge(birthDate: Date): number {
// ...
}
let birthDate: any = '1997-09-12';
calculateAge(birthDate); // 정상 (🚨 추후 에러 발생 가능)
any 타입에는 언어 서비스가 적용되지 않는다
any 타입은 코드 리팩터링 때 버그를 감춘다
any는 타입 설계를 감춰버린다
any는 타입시스템의 신뢰도를 떨어뜨린다