- 1장에서는 타입스크립트의 큰 그림을 이해하는데 도움이 될 내용을 다룹니다.
본격적인 내용에 앞서 타입스크립트란 무엇이고, 타입스크립트를 어떻게 여겨야 하는지,
자바스크립트와는 어떤 관계인지, 타입스크립트의 타입들은 null이 가능한지,
any 타입에서는 어떨지, 덕 타이핑(duck typing)이 가능한지 등을 알아봅니다.
- 타입스크립트는 사용 방식 면에서 조금은 독특한 언어입니다.
인터프리터(파이썬이나 루비 같은)로 실행되는 것도 아니고,
저수준 언어로 컴파일(자바나 C 같은)되는 것도 아닙니다.
- 또 다른 고수준 언어인 자바스크립트로 컴파일되며, 실행 역시 타입스크립트가 아닌 자바스크립트로 이루어집니다.
그래서 타입스크립트와 자바스크립트의 관계는 필연적이며,
이 밀접한 관계 때문에 혼란스러운 일이 벌어지기도 합니다.
이러한 타입스크립트와 자바스크립트의 관계를 잘 이해한다면 타입스크립트 개발자로서 한 단계 성장할 수 있습니다.
- 타입스크립트의 타입 시스템도 조금 독특한 특징을 가지고 있는데, 이 특징들은 자세히 알아둬야 합니다.
타입 시스템에 관해서는 뒤에서 자세히 다룰텐데, 여기서는 몇 가지 주목할 만한 점을 미리 짚고 넘어가겠습니다.
-
아이템1. 타입스크립트와 자바스크립트의 관계 이해하기
- 타입스크립트를 사용 중인 분들이라면, "타입스크립트는 자바스크립트의 상위 집합(superset)이다." 또는 "타입스크립트는 타입이 정의된 자바스크립트의 상위 집합이다"라는 말을 한 번쯤은 들어봤을 것입니다. 이 문장들이 정확히 무슨 의미인지, 그리고 타입스크립트와 자바스크립트는 어떤 관계인지 자세히 알아볼 것입니다.
- 타입스크립트는 자바스크립트와 굉장히 밀접한 관계에 있기 때문에, 서로 어떻게 연관되어 있는지 제대로 이해하는 것이 중요합니다.
- 타입스크립트는 문법적으로도 자바스크립트의 상위집합입니다.
자바스크립트 프로그램에 문법 오류가 없다면, 유효한 타입스크립트 프로그램이라고 할 수 있습니다.
그런데 자바스크립트 프로그램에 어떤 이슈가 존재한다면 문법 오류가 아니라도 타입 체커에 지적당할 가능성이 높습니다.
- 그러나 문법의 유효성과 동작의 이슈는 독립적인 문제입니다.
타입스크립트는 여전히 작성된 코드를 파싱하고, 자바스크립트로 변환할 수 있습니다.
- 자바스크립트 파일이 .js 확장자를 사요하는 반면, 타입스크립트 파일은 .ts(또는 .tsx) 확장자를 사용합니다.
그렇다고 자바스크립트와 타입스크립트가 완전히 다른 언어라는 의미는 아닙니다.
타입스크립트는 자바스크립트의 상위집합이기 때문에, .js 파일에 있는 코드는 이미 타입스크립트라고 할 수 있습니다.
main.js 파일명을 main.ts로 바꾼다고 해도 달라지는 것은 없습니다.
- 이러한 특성은 기존에 존재하는 자바스크립트 코드를 타입스크립트로 마이그레이션(migration)하는 데 엄청난 이점이 됩니다.
기존 코드를 그대로 유지하면서 일부분에만 타입스크립트 적용이 가능하기 때문입니다.
이에 비해 자바스크립트로 만든 프로그램을 자바 같은 완전히 다른 언어로 바꾸려면 처음부터 다시 작성하는게 빠를 겁니다.
- 모든 자바스크립트 프로그램이 타입스크립트라는 명제는 참이지만,
그 반대는 성립하지 않습니다. 타입스크립트 프로그램이지만 자바스크립트가 아닌 프로그램이 존재합니다.
이는 타입스크립트가 타입을 명시하는 추가적인 문법을 가지기 때문입니다.
- 예를 들어, 다음 코드는 유효한 타입스크립트 프로그램입니다.
function greet(who: string) {
console.log('Hello', who);
}
- 그러나 자바스크립트를 구동하는 노드(node) 같은 프로그램으로 앞의 코드를 실행하면 오류를 출력합니다.
function greet(who: string) {
SyntaxError: Unexpected token :
- : string은 타입스크립트에서 쓰이는 타입 구문입니다.
타입 구문을 사용하는 순간부터 자바스크립트는 타입스크립트 영역으로 들어가게 됩니다.
- 타입스크립트 컴파일러는 타입스크립트뿐만 아니라 일반 자바스크립트 프로그램에도 유용합니다.
다음 자바스크립트 프로그램을 예로 들어 보겠습니다.
let city = 'new york city';
console.log(city.toUppercase());
- 이 코드를 실행하면 다음과 같은 오류가 발생합니다.
TypeError: city.toUppercase is not a function
- 앞의 코드에는 타입 구문이 없지만, 타입스크립트의 타입 체커는 문제점을 찾아 냅니다.
let city = 'new york city';
console.log(city.toUppercase());
// 'toUppercase' 속성이 'string' 형식에 없습니다.
// 'toUpperCase'을(를) 사용하시겠습니까?
- city 변수가 문자열이라는 것을 알려주지 않아도, 타입스크립트는 초깃값으로부터 타입을 추론합니다.
타입 추론은 타입스크립트에서 중요한 부분입니다.
- 타입 시스템의 목표 중 하나는 런타임에 오류를 발생시킬 코드를 미리 찾아내는 것입니다.
타입스크립트가 '정적' 타입 시스템이라는 것은 바로 이런 특징을 말하는 것입니다.
그러나 타입 체커가 모든 오류를 찾아내지는 않습니다.
const states = [
{name: 'Alabama', capital: 'Montgomery'},
{name: 'Alaska', capital: 'Juneau'},
{name: 'Arizona', capital: 'Phoneix'},
];
for (const state of states) {
console.log(state.capitol);
}
- 실행 결과는 다음처럼 출력됩니다.
undefined
undefined
undefined
- 앞의 코드는 유효한 자바스크립트(또한 타입스크립트)이며 어떠한 오류도 없이 실행됩니다.
그러나 루프 내의 state.capitol은 의도한 코드가 아닌게 분명합니다.
이런 경우에 타입스크립트 타입 체커는 추가적인 타입 구문 없이도 오류를 찾아냅니다.
(또한 꽤 훌륭한 해결책을 제시합니다)
for (const state of states){
console.log(state.capitol);
// 'capitol' 속성이 ... 형식에 없습니다.
// 'capital'을(를) 사용하시겠습니까?
}
- 타입스크립트는 타입 구문 없이도 오류를 잡을 수 있지만, 타입 구문을 추가한다면
훨씬 더 많은 오류를 찾아낼 수 있습니다.
코드의 '의도'가 무엇인지 타입 구문을 통해 타입스크립트에게 알려줄 수 있기 때문에
코드의 동작과 의도가 다른 부분을 찾을 수 있습니다.
예를 들어, 다음과 같이 capital과 capitol을 맞바꾸어 보겠습니다.
const states = [
{name: 'Alabama', capitol: 'Montgomery'},
{name: 'Alaska', capitol: 'Juneau'},
{name: 'Arizona', capitol: 'Phonix'},
];
for (const state of states){
console.log(state.capital);
// 'capital' 속성이 ... 형식에 없습니다.
// 'capitol'을 사요하시겠습니까?
}
- 그런데 타입스크립트가 제시한 해결책은 잘못되었습니다.
한 곳에서는 capital로, 다른 한 곳에서는 capitol로 다르게 타이핑했지만,
타입스크립트는 어느 쪽이 오타인지 판단하지 못합니다.
- 오류의 원인을 추측할 수는 있겠지만, 항상 정확하지는 않습니다.
따라서 명시적으로 states를 선언하여 의도를 분명하게 하는 것이 좋습니다.
inteface State {
name: string;
capital: string;
}
const states: State[] = [
{name: 'Alabama', capitol: 'Montgomery'},
{name: 'Alaska', capitol: 'Juneau'},
{name: 'Arizona', capitol: 'Phoenix'},
// 개체 리터럴은 알려진 속성만 지정할 수 있지만
// 'State' 형식에 'capitol'이(가) 없습니다.
// 'capital'을(를) 쓰려고 했습니까?
// ...
];
for (const state of states) {
console.log(state.capital);
}
- 이제 오류가 어디에서 발생했는지 찾을 수 있고, 제시된 해결책도 올바릅니다.
의도를 명확히 해서 타입스크립트가 잠재적 문제점을 찾을 수 있게 했습니다.
예를 들어, 타입 구문 없이 배열 안에서 딱 한 번 capitol이라고 오타를 썼다면 오류가 되지 않았을 겁니다.
그런데 타입 구문을 추가하면 오류를 찾을 수 있습니다.