타입스크립트는 문법적으로 자바스크립트의 상위 집합이다.
모든 자바스크립트 프로그램이 타입스크립트라는 명제는 참이지만, 그 반대는 성립하지 않는다.
타입스크립트 프로그램이지만 자바스크립트가 아닌 프로그램도 존재한다.
이는 타입스크립트가 타입을 명시하는 추가적인 문법을 가지기 때문이다.
예를 들어,
function greet(who: string) {
console.log('Hello', who);
}
위 코드는 유효한 타입스크립트 프로그램이다.
그러나 자바스크립트를 구동하는 노드같은 프로그램으로 앞의 코드를 실행하면 오류를 출력한다.
:string
은 타입스크립트에서 쓰이는 타입 구문이기 때문이다.
타입 구문을 사용하는 순간부터 자바스크립트는 타입스크립트 영여긍로 들어가게 된다.
타입스크립트 컴파일러는 타입스크립트뿐만 아니라, 일반 자바스크립트 프로그램에도 유용하다.
let city = 'new york city';
console.log(city.toUppercase());
이 코드를 실행하면, Type Error: city.toUppercase is not a function
과 같은 오류가 발생한다.
앞의 코드에는 타입 구문이 없지만, 타입스크립트의 타입 체커는 문제점을 찾아낸다.
city 변수가 문자열이라는 것을 알려주지 않아도 타입스크립트는 초깃값으로부터 타입을 추론한다.
타입 시스템의 목표 중 하나는 런타임에 오류를 발생시킬 코드를 미리 찾아내는 것이다.
타입스크립트가 정적타입 시스템이라는 것은 바로 이런 특징을 말하는 것이다. 그러나 타입 체커가 모든 오류를 찾아내지는 않는다.
오류가 발생하지는 않지만 의도와 다르게 동작하는 코드 또한 타입스크립트는 잡아낼 수 있다.
const states = [
{name: 'Alabama', capital: 'Montgomery'},
{name: 'Alaska', capital: 'Juneau'},
{name: 'Arizona', capital: 'Phoenix'},
];
for (const state of states) {
console.log(state.capitol);
}
앞의 코드는 유효한 자바스크립트이며 어떠한 오류도 없이 실행된다.
그러나 루프 내의 state.capitol은 의도한 코드가 아닌 게 분명하다.
이런 경우 타입스크립트의 타입 체커는 추가적인 타입 구문 없이도 오류를 찾아 내며 꽤나 훌륭한 해결책을 제시한다.
for (const state of states){
console.log(state.capitol) // 'capito' 속성이 형식에 없습니다. 'capital'을 사용하시겠습니까?
}
타입스크립트는 타입 구문 없이도 오류를 잡을 수 있지만, 타입 구문을 추가한다면 훨씬 더 많은 오류를 찾아낼 수 있다. 코드의 의도가 무엇인지 타입 구문을 통해 타입스크립트에게 알려줄 수 있기 때문에 코드의 동작과 의도가 다른 부분을 찾을 수 있다.
하지만, 타입스크립트는 오류의 원인을 추측할 수는 있지만, 어느쪽이 오타인지 판단하지 못한다. 오류의 원인을 추측하지만 항상 정확하게 찾아내지는 못한다는 얘기다.
따라서 명시적으로 states를 선언하여 의도를 분명하게 하는 것이 좋다.
interface State {
name: string;
capital: string;
}
const states: state[] = [
[
{name: 'Alabama', capitol: 'Montgomery'},
{name: 'Alaska', capitol: 'Juneau'},
{name: 'Arizona', capitol: 'Phoenix'},
] // 개체 리터럴은 알려진 속성만 지정할 수 있지만
// 'State'형식에 'capitol'이 없습니다.
// 'capital'을 쓰려고 했습니까?
이제 오류가 어디에서 발생했는지 찾을 수 있고, 제시된 해결책도 올바르다.
의도를 명확히 해서 타입스크립트가 잠재적 문제점을 찾을 수 있게 만든것이다.
타입스크립트 타입 시스템은 자바스크립트의 런타임 동작을 모델링한다.
그러나 앞에서 봤던 경우들처럼 단순히 런타임 동작을 모델링하는 것뿐만 아니라, 타입스크립트는 의도치 않은 이상한 코드가 오류로 이어질 수도 있다는 점까지 고려한다.
앞서 capital과 capitol 예제에서도 보았듯이 프로그램에 오류가 발생하지 않더라도 타입 체커가 오류를 표시한다.
언제 자바스크립트 런타임 동작을 그대로 모델링할지, 또는 추가적인 타입 체크를할지 분명하지 않다면 과연 타입스크립트를 사용해도 되는지 의문이 들 수 있다.
작성된 프로그램이 타입 체크를 통과하더라도 여전히 런타임에 오류가 발생할 수 있다.
const names = ['Alice', 'Bob'];
console.log(names[2].toUpperCase());
//TypeError: Cannot read Property 'toUpperCase' of undefined
타입스크립트는 앞의 배열이 범위 내에서 사용될 것이라 가정했지만 실제로는 그렇지 않았고 오류가 발생했다.
앞서 등장한 오류들이 발생하는 근본 원인은 타입스크립트가 이해하는 값의 타입과 실제 값에 차이가 잇기 때문이다.
타입 시스템이 정적 타입의 정확성을 보장해 줄 것 같지만 실제로는 그렇지 않다.
애초에 타입 시스템은 그런 목적으로 만들어지지도 않았다.
정확성을 보장하는 것이 중요하다면 Reason이나, Elm같은 언어를 선택하는 것이 좋다. 이 언어들은 런타임 안정성을 보장하는 대신, 자바스크립트의 상위집합이 아니기 때문에 마이그레이션 과정이 훨씬 복잡하다.
타입스크립트는 자바스크립트 런타임 동작을 모델링하는 타입 시스템을 가지고 있기 때문에 런타임 오류를 발생시키는 코드를 찾아내려고 하지만 모든 오류를 찾아내리라 기대하면 안된다.
타입스크립트의 타입 시스템은 전반적으로 자바스크립트 동작을 모델링하지만 자바스크립트에서 허용되는 문법을 문제삼기도 한다.
다음 코드가 오류 없이 타입 체커를 통과할 수 있을지 생각해 보자.
function add(a,b) {
return a + b;
}
add(10,null);
TS 설정이 어떻게 되어 있는지 모른다면 대답할 수 없는 질문이다.
TS 컴파일러는 매우 많은 설정을 갖고 있다.
이 설정들은 커맨드 라인에서 사용할 수 있다.
tsc --noImplicitAny program.ts
tsconfig.json 설정 파일을 통해서도 가능하다.
{
"compileOptions: {
"noImplicitAny": true
}
}
가급적 설정 파일을 사용하는 것이 좋다.
그래야만 타입스크립트를 어덯게 사용할 계획인지 동료들이나 다른 도구들일 알 수 있다.
설정 파일은 tsc --init
만 실행하면 간단히 생성한다.
타입스크립트의 설정들은 어디서 소스 파일을 찾을지, 어떤 종류의 출력을 생성할지 제어하는 내용이 대부분이다.
그런데 언어 자체의 핵심 요소들을 제어하는 설정도 있다.
대부분의 언어에서는 허용하지 않는 고수준 설계의 설정이다.
타입스크립트는 어떻게 설정하느냐에 따라 완전히 다른 언어처럼 느껴질 수 있다.
설정을 제대로 사용하려면 noImplicitAny
와 strictNullChecks
를 이해해야 한다.
noImplicitAny
는 변수들이 미리 정의된 타입을 가져야 하는지 여부를 제어한다.
다음 코드는 noImplicitAny
가 해제되어 있을 때에는 유효한 코드다
function add(a,b) {
return a + b;
}
// type : function add(a: any, b: any): any
any 타입을 매개변수에 사용하면 타입 체커는 무력해진다.
any는 유용하지만, 매우 주의해서 사용해야 한다.
function add (a,b) {
// ~ 'a' 매개변수에는 암시적으로 `any`형식이 포함됩니다.
// ~ 'b' 매개변수에는 암시적으로 'any'형식이 포함됩니다.
return a + b ;
}
any를 코드에 넣지 않았지만 any 타입으로 간주하기 때문에 이를 암시적 any 라고 부른다.
그런데 같은 코드임에도 noImplicitAny
가 설정되었다면 오류가 된다.
이 오류들은 명시적으로 : any
를 선언하거나, 더 분명한 타입을 사용하면 해결할 수 있다.
function add (a: number, b: number){
return a + b;
}
타입스크립트는 타입 정보를 가질 때 가장 효과적이기 때문에 되도록 noImplicitAny
를 설정해야 한다.
모든 변수에 타입을 명시하는 것에 익숙해지면 noImplicitAny
가 없는 채로 작성된 타입스크립트는 완전히 다른 언어처럼 느껴질 겁니다.
새 프로젝트를 시작한다면 처음부터 noImplicitAny
를 설정하여 코드를 작성할 때마다 타입을 명시하도록 해야한다.
그러면 타입스크립트가 문제를 발견하기 수월해지고, 코드의 가독성이 좋아지며, 개발자의 생산성이 향상된다.
noImplicitAny
설정 해제는, 자바스크립트로 되어 있는 기존 프로젝트를 타입스크립트로 전환하는 상황에만 필요하다.
strictNullChecks
는 null과 undefined가 모든 타입에서 허용되는지 확인하는 설정이다.
다음은 strictNullChecks
가 해제되었을 때 유효한 코드이다.
const x: number = null; // 정상. null은 유효한 값이다.
그러나 strictNullChecks
를 설정하면 오류가 된다.
const x: number = null; // ~'null'형식은 'number'에 할당할 수 없습니다.
null 대신 undefined를 써도 같은 오류가 난다.
만약 null을 허용하려고 한다면, 의도를 명시적으로 드러냄으로써 오류를 고칠 수 있다.
const x: number | null = null;
strictNullchecks
설정 없이 개발하기로 선택했다면, undefined는 객체가 아닙니다 라는 끔찎한 런타임 오류를 자주 맞이할 것이다.
결국 이 오류 때문에 엄격한 체크를 설정할 수밖에 없을 것이다.
프로젝트가 거대해질수록 설정 변경은 어려워질 것이므로 가능한 한 초반에 설정하는게 좋다.
언어에 의미적으로 영향을 미치는 설정들이 많지만,
strictNullchecks
와 noImplicitAny
만큼 중요한 것은 없다.
이 모든 체크를 설정하고 싶다면 strict
설정을 하면 된다.
타입스크립트에 strict 설정을 하면 대부분의 오류를 잡아낸다.
공동 프로젝트를 진행하는 도중 동료에게 타입스크립트 예제를 공유했는데 오류가 재현되지 않는다면, 컴파일러 설정이 동일한지 먼저 확인해 봐야 한다.
타입스크립트 컴파일러는 언어의 핵심 요소에 영향을 미치는 몇 가지 설정을 포함하고 있다.
타입스크립트 설정은 커멘드 라인을 이용하기보다는 tsconfig.json을 사용하는 것이 좋다.
JS프로젝트를 TS로 전환하는게 아니라면 noImplicitAny
를 설정하는 것이 좋다.
undefined
는 객체가 아닙니다 와 같은 런타임 오류를 방지하기 위해서 strictNullChecks를 설정하는 것이 좋다.
타입스크립트에서 엄격한 체크를 하고 싶다면 strict 설정을 고려해야 한다.