
1. 타입 스크립트 알아보기
JS는 TS의 부분 집합이다.
이에 따라 마이그레이션에 엄청난 이점이 된다.
타입 추론
let city = 'new york city';
console.log(city.toUppercase());
typeError: city.toUppercase is not a function
city 변수가 문자열이라는 것을 알려주지 않아도 TS는 초깃값으로부터 타입을 추론하여 오류를 잡아낸다. 타입시스템의 목표 중 하나는 런타임에 오류를 발생시킬 코드를 미리 찾아내는 것이며, 타입스크립트가 '정적' 타입 시스템이라는 것은 바로 이런 특징을 말한다.
아래 예시와 같이 명시적 선언을 통해 의도를 분명히하여 오류의 원인을 정확히 잡아낼 수 있게 할 것

TS의 타입 시스템은 JS의 런타임 동작을 모델링한다.
따라서 런타임 오류를 발생시키는 코드를 찾아내려 한다. 하지만 타입 체커를 통과하면서도 런타임 오류를 발생시키는 코드도 충분히 존재한다....
동료나 다른 도구들이 알기 쉽게 tsconfig.json 설정 파일을 이용하도록 하자.
tsc --init 으로 간단히 생성
noImplicitAny : 변수들이 미리 정의된 타입을 가져야 하는지 여부를 제어
! TS는 타입 정보를 가질 때 가장 효과적이므로 되도록이면 true로 설정하자.
! 이 설정 해제는 JS로 되어 있는 기존 프로젝트를 TS로 전환할 때만 필요하다.
strictNullChecks : null과 undefined가 모든 타입에서 허용되는지 확인하는 설정
! null , undefined 관련 오류 잡아 내는 데 도움이 많이 되나 코드 작성이 어려워질거다..
! 결국 "undefined는 객체가 아닙니다" 같은 끔찍한 오류를 보기 싫으면 처음부터 해보라신다.

strict 설정하면 위와 같은 설정들이 모두 포함된다. 대부분의 오류를 잡아낼 수 있음.
타입 스크립트 컴파일러의 역할
1. 최신 TS/JS를 브라우저에서 동작할 수 있또록 구버전의 JS로 트랜스파일한다.
2. 코드의 타입 오류를 체크
위 두 행위가 독립적으로 이루어진다.
따라서 TS > JS 로 변환과정 및 JS 실행 시점에서 코드 내의 타입에는 영향을 주지 않는다.
트랜스파일(transpile) : translate + compile. 소스코드를 동일한 동작을 하는 다른 형태의 소스코드로 변환하는 행위. 결과물은 여전히 컴파일 되어야 함.
✔️타입 오류가 있는 코드도 컴파일이 가능하다
컴파일은 타입 체크과 독립적으로 동작하므로 타입 오류가 있는 코드도 컴파일이 가능하다.
이게 엉성해보일 수 있지만 실제로 도움이 된다.
>> 타입 오류를 수정하지 않고 컴파일된 산출물로 다른 부분을 테스트해볼 수 있음.
! 만약 오류가 있을 때 컴파일하지 않으려면 **tsconfig.json의 noEmitOnError**를 설정
✔️런타임에는 타입체크가 불가하다
타입은 런타임간 체크할 수가 없기 때문에 타입 정보를 유지하는 기법이 필요하다.
속성이 존재하는지 체크

런타임에 접근 가능한 타입 정보를 명시적으로 저장하는 '태그' 기법

타입을 클래스로 만들어 타입과 값을 둘 다 사용하는 기법

type Shape = Square | Rectangle에서 Rectangle은 타입으로 참조되지만 instanceof 부분에서는 값으로 참조된다. 이 부분은 item 8에서 다룬다
✔️타입 연산은 런타임에 영향을 주지 않는다
값을 정제하기 위해서는 런타임의 타입을 체크해야 하고 JS연산을 통해 변환을 수행해야 한다. 아래 예시에서 변환된 파일을 보면 그 의미를 알 수 있다.

// JS 연산을 통해 변환을 수행해야 한다.
function asNumber(val: number | string) : number {
return typeof(val) === 'string' ? Number(val) : val;
}
✔️런타임 타입은 선언된 타입과 다를 수 있다
TS에서의 타입은 런타임에 제거되기 때문에 TS의 타입은 JS로 변환되며 삭제되기 때문에 런타임 타입과 선언된 타입이 맞지 않을 수 있다.
function setLightSwitch(value :boolean) {
switch (value) {
case true:
console.log('on')
break;
case false :
console.log('off')
break;
default:
console.log('X')
}
}
interface LightApi {
lightSwitch : boolean
}
async function setLight() {
const res = await fetch('/light');
const result : LightApi = await res.json();
setLightSwitch(result.lightSwitch)
}
위 예시에선 fetch로 받아온 결과를 LigthApi 타입으로 반환하라 선언했지만, 실제로 그렇게 되리라는 보장이 없다. 실제로 result.lightSwitch 타입이 string이어도 런타임에는 setLightSwitch까지 전달될 것이다.
✔️타입스크립트 타입으로는 함수를 오버로드 할 수 없다
함수 오버로딩 : 동일한 이름에 매개변수만 다른 여러 버전의 함수
TS가 함수 오버로딩 기능을 지원하긴 하지만, 온전히 타입 수준에서만 동작한다.
즉, 하나의 함수에 여러 개의 선언문을 작성할 수 있으나 구현체는 오직 하나 뿐이다.
✔️타입스크립트 타입은 런타임 성능에 영향을 주지 않는다
타입과 타입 연산자는 JS 변환 시점에 제거되기 때문에, 런타임의 성능에 아무런 영향을 주지 않는다.
JS는 본질적으로 덕 타이핑 기반이다. 만약 어떤 함수의 매개변수 값이 모두 제대로 주어진다면, 그 값이 어떻게 만들어졌는지 신경 쓰지 않고 사용한다.
덕 타이핑 : 객체가 어떤 타입에 부합하는 변수와 메서드를 가질 경우 객체를 해당 타입에 속하는 것으로 간주
TS는 이런 동작, 즉 매개변수 값이 요구사항을 만족한다면 타입이 무엇인지 신경 쓰지 않는 동작을 그대로 모델링한다. (구조적 타이핑)
이 구조적 타이핑 때문에 문제가 발생하기도 한다.
interface Vector2D {
x: number;
y: number;
}
function calculateLength(v: Vector2D) {
return Math.sqrt(v.x * v.x + v.y * v.y);
}
interface Vector3D {
x: number;
y: number;
z: number;
}
function normalize(v: Vector3D) {
const length = calculateLength(v);
return {
x: v.x / length;
y: v.y / length;
z: v.z / length;
}
}
위 예시에서 calculateLength 에 v값이 Vector3D 타입으로 넘어갔는데도 오류가 발생하지 않는 이유는 구조적 타이핑 관점에서 x와 y의 존재로 Vector2D와 호환되었기 때문이다.
타입은 좋든 싫든 열려있다. 봉인되어 있지 않다 (확장)
++ 클래스 역시 구조적 타이핑 규칙을 따르며 클래스의 인스턴스가 예상과 다를 수 있다.
++ 구조적 타이핑을 사용하면 유닛 테스팅을 손쉽게 할 수 있다. (아직 어렵다..)
TS의 타입 시스템은 코드에 타입을 조금씩 추가할 수 있기 때문에 점진적이며, 언제든지 타입 체커를 해제할 수 있기 때문에 선택적이다. 이 기능의 핵심은 any 타입
무분별한 any 타입사용으로 '확장 가능한 자바스크립트' 라는 모토를 깨지 말자.
✔️any 타입에는 타입 안정성이 없다.
선언된 타입을 as any를 사용해 string으로 할당하게 되면 타입 체커는 선언에 따라 number로 판단할 것이고 혼돈이 올 것이다.
let age:number;
age = '12';
// ~~~ '"12"' 형식은 'number' 형식에 할당할 수 없습니다.
age = '12 as any;
age += 1; // 런타임은 정상, age => '121'
✔️any는 함수 시그니처를 무시한다.
호출하는 쪽은 약속된 타입의 입력을 제공하고, 함수는 약속된 타입의 출력을 반환해야 하지만 any 타입을 사용하면 이를 무시할 수 있다.
✔️any 타입에는 언어 서비스가 적용되지 않는다.
any 타입을 사용하면 자동완성 기능과 적절한 도움말을 제공받을 수 없다.
생산성이 매우 낮아질 것.
✔️any 타입은 코드 리팩터링 시 버그를 감춘다.
타입 체크를 통과했다고 해서 끝난게 아니다. any를 통해 잘못된 매개변수를 받아 타입체커는 통과함에도 불구하고 런타임에 오류가 발생할 수 있다.
✔️any는 타입 설계를 감춰버린다.
애플리케이션 상태 같은 객체를 정의하려면 꽤 복잡하지만 any를 통해 간단히 끝낼 수 있다. 하지만 타입 설계가 불분명해진다. 동료가 코드를 검토한다면 코드부터 재구성하는 수고를 하게 될 것이다. 타입을 일일이 작성하는 것이 좋다.
✔️any는 타입시스템의 신뢰도를 떨어뜨린다.
타입 체커가 실수를 잡아주고 코드의 신뢰도를 높혀준다. 그러나 런타임에 타입 오류를 발견하게 된다면 타입 체커의 의미가 없어진다. any타입을 쓰지 않으면 런타임 오류를 미리 잡을 수 있을 것.