🙋 why typescript?
자바스크립트는 언제 실수를 알려주나? 바로 프로그램을 실행할 때다. 타입스크립트가 에러를 알려준다는 사실 자체보다 더 훌륭한 것은 에러를 알려주는 시점. 개발자가 텍스트 편집기에 코드를 입력하는 순간 곧바로 에러 메세지를 발생시킨다.
프로그램은 프로그래머가 작성한 다수의 텍스트 파일로 구성.
이 텍스트를 컴파일러라는 특별한 프로그램이 파싱하여 추상 문법 트리(abstract syntax tree, AST)라는 자료구조로 변환 ➡️ AST는 공백, 주석을 완전히 무시한다 ➡️ 그리고 컴파일러는 다시 AST를 바이트코드라는 하위 수준의 표현으로 변환 ➡️ 바이트 코드가 만들어졌으면 런타임(runtime)이라는 다른 프로그램에 바이트코드를 입력해 평가하고 결과를 얻을 수 있다.
즉 프로그램을 실행한다는 것은 컴파일러가 소스 코드를 파싱해 AST로 만들고 다시 AST를 바이트 코드로 변환한 것을 런타임이 평가하도록 지시하는 것.
타입스크립트 컴파일 및 실행
TS
1. 타입스크립트 소스 ➡️ 타입스크립트 AST
2. 타입검사기가 AST를 확인
3. 타입스크립트 AST ➡️ 자바스크립트 소스
JS
4. 자바스크립트 소스 ➡️ 자바스크립트 AST
5. AST ➡️ 바이트코드
6. 런타임이 바이트코드를 평가
1~3은 TSC가 수행하며, 과정 4~6은 브라우저, NodeJS, 기타 자바스크립트 엔진과 같은 런타임이 실행한다. 1~1의 과정에는 소스 코드에 사용된 타입을 이용하지만 과정 3에서는 이용하지 않는다. 다시 말해, TSC가 타입스크립트 코드를 자바스크립트 코드로 컴파일 할 때 타입확인 ❌.
개발자가 기입한 타입 정보는 최종적인 프로그램에는 아무런 영향을 주지 않으며 타입 확인에만 사용된다.
typechecker
코드의 타입 안정성을 검증하는 특별한 프로그램
최신 언어는 저마다의 타입 시스템을 갖추고 있다.
타입 시스템(type system)
타입 검사기가 프로그램에 할당하는 데 사용하는 규칙 집합
타입 시스템은 보통 두 가지 종류, 즉 어떤 타임을 사용하는지를 컴파일러에 명시적으로 알려주는 타입 시스템과 자동으로 타입으로 추론하는 타입 시스템으로 구분된다. 타입스크립트는 두 가지 시스템 모두의 영향을 받았다. 즉, 개발자는 타입을 명시하거나 타입스크립트가 추론하도록 하는 방식 중에서 선택할 수 있다.
어노테이션을 이용하면 타입스크립트에 명시적으로 타입을 지정. 어노테이션은 'value:type' 형태로 쓰이며 타입 검사기에 알려주는 역할!
어노테이션을 사용하지 않으면 타입스크립트가 알아서 타입을 추론한다. 생각보다 잘함!
타입스크립트가 타입을 추론하도록 두는 것이 코드를 줄일 수 있는 방법이므로 보통 어노테이션은 생략한다.
타입 시스템 기능 | 자바스크립트 | 타입스크립트 |
---|---|---|
타입 결정 방식 | 동적 | 정적 |
타입이 자동으로 변환되는가? | O | X(대부분) |
언제 타입을 확인하는가? | 런타임 | 컴파일 타임 |
언제 에러를 검출하는가? | 런타임(대부분) | 컴파일 타임(대부분) |
동적 타입 바인딩이란 프로그램을 실행해야만 특정 데이터 타입을 알 수 있음을 의미. 자바스크립트는 프로그램을 실행하기 전에는 타입을 알 수 없다.(무수한console.log의 기억) ⭐ 타입스크립트는 점진적으로 타입을 확인하는(gradually typed) 언어⭐ 타입스크립트는 타입을 지정하지 않은 프로그램이라도 그중 일부 타입을 추론해 오류를 검출할 수 있지만 모든 타입을 알지 못하는 상황에서는 많은 오류가 노출될 수 있다.
자바스크립트는 약한 타입 언어. 그래서 개발자의 의도를 최대한 알아내려 노력하고 결과를 도출. 반면 타입스크립트는 유효하지 않은 작업을 발견하면 바로 에러.
컴파일 타임에 코드의 타입을 확인하기에 코드를 실행하지 않고도 이전 예제 코드에 에러가 있음을 바로 알 수 있다.
자바스크립트는 런타임에 예외를 던지거나 암묵적 형변환 수행. 타입스크립트는 컴파일 타임에 문법 에러와 타입에러를 모두 검출. 그러나 스택 오버플로, 네트워크 등 하지 못하는 에러도 존재한다.
# TSC, TSLint, NodeJS용 타입 선언 설치
npm install --save-dev typescript tslint @types/node
모든 타입스크립트 프로젝트 루트 디렉터리에 tsconfig.json파일이 존재해야 한다. 타입스크립트 프로젝트에서 어떤 파일을 컴파일하고 어떤 자바스크립트 버전으로 방출하는지를 정의.
옵션 | 설명 |
---|---|
include | TSC가 타입스크립트 파일을 찾을 디렉터리 |
lib | TSC가 코드 실행환경에서 이용할 수 있다고 가정하는 API |
module | TSC가 코드를 컴파일 할 때 대상 모듈 시스템(CommonJS, ES2015 등) |
outDir | 생성된 자바스크립트 코드를 출력할 디렉터리 |
strict | 유효하지 않은 코드를 확인할 때 가능한 한 엄격하게 감시 |
target | TSC가 코드를 컴파일할 자바스크립트 버전 |
보통 프로젝트는 TSlint 설정을 정의하는 tslint.json 파일도 포함한다.
타입
값과 이 값으로 할 수 있는 일의 집합
타입스크립트란 특정 타입만 와야할 때 이를 명시할 수 있는 언어. 숫자가 아닌 타입을 건네면 타입스크립트가 바로 에러를 발생시킨다. 타입 어노테이션이 없으면 squareOfdml 매개변수에 제한이 없으므로 아무 타입이나 인수로 전달할 수 있다. 일단 타입을 제한하면 타입스크립트가 함수를 호출할 때 호환이 되는 인수로 호출했는지를 판단.
타입스크립트가 지원하는 타입을 살펴보며 무엇을 포함할 수 있는지 살펴보자.
실제 프로그래밍에서는 보통 1,2 방법을 사용. 이렇게하면 타입 리터럴이 된다. (boolean 타입 중 특정한 하나의 값으로 한정)
number타입은 모든 숫자의 집합(정,소,양,음,Infinity, NaN)
boolean처럼 개발자들은 대개 number 타입을 추론하도록. number 타입임을 명시해야 하는 상황은 거의 없다.
ES2015에 새로 추가된 기능. 실무에서는 자주 사용하지 않는 편이며 객체와 맵에서 문자열 키를 대신하는 용도로 사용된다. 심벌 키를 사용하면 사람들이 잘 알려진 키만 사용하도록 강제할 수 있으므로 키를 잘못 설정하는 실수를 방지. 객체의 기본 반복자(Symbol.iterator)를 설정하거나 객체가 어떤 인스턴스인지(Symbol.hasInstance)를 런타임에 오버라이딩하는 것과 비슷한 기능을 제공.
타입스크립트의 객체(object) 타입은 객체의 형태(shape)를 정의한다. 객체 타입만으로는 객체리터럴로 만든 객체와 new로 생성한 객체를 구분할 수 없다. 자바스크립트가 구조 기반 타입을 갖도록 설계되었기 때문이다.
구조 기반 타입화
구조 기반 타입화에서는 객체의 이름에 상관없이 객체가 어떤 프로퍼티를 갖고 있는지 따진다(이름 기반 타입에서는 이름을 따진다) 일부 언어에서는 덕 타이핑(duck typing)이라고 한다(겉표지만 보고 책을 판단하지 않는 것과 같은 원리)
b에접근하면 에러가 발생. 사실 object는 any보다 조금 더 좁은 타입. object는 서술하는 값에 관한 정보를 거의 알려주지 않으며 값 자체가 자바스크립트 객체라고 말해줄 뿐이다.
타입스크립트가 c의 형태를 추론하게 하거나 중괄호({})안에 타입을 명시적으로 묘사할 수 있다.
객체를 const로 선언할 때의 타입 추론
객체를 const로 선언하면 어떻게 될까?
- 타입스크립트가 b를 리터럴 12가 아닌 number로 추론. 이를 통해 const를 사용하느냐 let을 사용하느냐 const를 사용하느냐에 따라 타입스크립트가 number나 string 타입을 추론.
- 하지만 지금까지 살펴본 기본 타입과 달리 객체를 const로 선언해도 타입스크립트는 더 좁은 타입으로 추론하지 않는다. 자바스크립트 객체의 값은 바뀔 수 있으며, 타입스크립트도 객체를 만든 후 필드 값을 바꾸려 할 수 있다는 사실을 알기 때문.
객체 리터럴 문법은 "이런 형태의 물건이 있어"라고 말한다. 이 물건은 객체 리터럴 또는 클래스일 수 있다.
기본적으로 타입스크립트는 객체 프로퍼티에 엄격한 편. 예를 들어 객체에 number 타입의 b라는 프로퍼티가 있어야 한다고 정의하면 타입스크립트는 오직 b만 기대한다. b가 없거나 다른 추가 프로퍼티가 있으면 에러를 발생시킨다.
그렇다면 어떻게 예정에 없던 프로퍼티가 추가될 수 있다고 알려줄 수 있을까?
인덱스 시그니처(index signature)
[key : T] : U같은 문법을 인덱스 시그니처라고 부르며 타입스크립에 어떤 객체가 여러 키를 가질 수 있음을 알려준다. "이 객체에서 모든 T 타입의 키는 U 타입의 값을 갖는다"라고 해석할 수 있다. 인덱스 시그니처를 이용하면 명시적으로 정의한 키 외에 다양한 키를 객체에 안전하게 추가할 수 있다.
- 인덱스 시그니처에서 기억해야 할 규칙이 하나 있다. 인덱스 시그니처의 키(T)는 반드시 number나 string 타입에 할당할 수 있는 타입이어야 한다
- 인덱스 시그니처의 이름은 원하는 이름을 가져다 바꿔도 된다. 즉, key가 아니어도 된다.
객체 타입을 정의할 때 선택형(?)만 사용할 수 있는 것은 아니다. 필요하면 readonly 한정자를 이용해 특정 필드를 읽기 전용으로 정의할 수 있다.( 즉, 정의한 필드에 초깃값을 할당한 다음에는 그 값을 바꿀 수 없다. 객체 프로퍼티에 const를 적용한 듯한 효과를 낸다)
객체리터럴 표기법에는 빈 객체 타입({})이라는 특별한 상황이 존재. null과 undefined를 제외한 모든 타입은 빈 객체 타입에 할당할 수 있으나, 이는 사용하기 까다롭게 만든다. 따라서 가능한 한 빈 객체는 피하는 것이 좋다.
마지막으로 객체:Object로 객체 타입을 만드는 방법도 있다. {}과 비슷한 방법이며 마찬가지로 가능하면 사용하지 않아야 한다. 타입스크립트에서 객체를 정의하는 방법은 다음처럼 네 가지로 요약할 수 있다.
🙋 타입스크립트 프로그램을 작성할 때 대부분 첫 번째나 세 번째 방법을 이용해야 한다. 두 번째와 네 번째 방법은 피해야 한다!