타입스크립트 프로그래밍 - 이론 공부
October 03, 2022
책 타입스크립트 프로그래밍으로 공부하면서 기억하고 싶은 내용을 정리했습니다. 정확하고 자세한 내용은 해당 책을 꼭 구매하셔서 읽어보시기 바랍니다.
컴파일러
- TSC(TypeScript Compiler) : TSC는 AST로 파싱한 후 자바스크립트 코드로 만들기 전에 타입을 체크(TypeChecker)한다.
 - TypeChecker : 코드의 타입 안전성을 검증하는 특별한 프로그램
 - 
TS 컴파일 및 실행 과정
- [TSC] 
타입스크립트소스를AST로파싱 - [TSC] 
TypeChecker가AST를타입체크✨ - [TSC] 
AST를자바스크립트소스로컴파일 
 - [TSC] 
 - 
JS 컴파일 및 실행 과정
- [컴파일러] 
자바스크립트소스를AST로파싱 - [컴파일러] 
AST를바이트코드로컴파일 - [런타임] 
런타임이바이트코드를평가 
 - [컴파일러] 
 - 
컴파일러 + 런타임 = 엔진
- 보통 자바스크립트 
컴파일러와런타임은엔진이라는 하나의 프로그램으로 합쳐진다. 이러한 동작을 통해 자바스크립트가 해석되는(interpreted) 언어의 모습을 갖게 만든다. 
 - 보통 자바스크립트 
 - 
TypeSystem
- 
TypeChecker가 프로그램을 타입에 할당하는 데 사용하는 규칙 집합
- 
어떤 타입을 사용하는지 컴파일러에 명시적으로 알려주는 타입 시스템
- 어노테이션 사용(value: type)
 
 - 
자동으로 타입을 추론하는 타입 시스템
- 타입스크립트가 타입을 추론하도록 두는 것이 코드를 줄일 수 있는 방법이기도 하다.
 
 
 - 
 
 - 
 - 자바스크립트가 제공하는 암묵적 변환 때문에 문제의 원인을 추적하기 어렵다. 따라서 타입을 변환할 때는 명시적으로 해야 한다.
 - 
에러
- 
런타임
- 자바스크립트는 런타임에 예외를 던지거나 암묵적 형변환을 수행한다.
 
 - 
컴파일 타임
- 
타입스크립트는 컴파일 타임에 SyntaxError와 TypeError를 모두 검출한다.
- IDE에서 코딩 시 실시간으로 이런 종류의 에러가 바로 표시된다. 타입스크립트처럼 점진적 컴파일을 지원하는 언어는 코드의 일부만 고쳤을 때에는 전체 프로그램을 다시 컴파일할 필요 없으므로 빨리 재컴파일된다.
 
 - 
타입스크립트가 컴파일 타임에 검출할 수 없는
런타임예외도 많다.- 예시 : 스택 오버플로, 네트워크 연결 끊김, 잘못된 사용자 입력 등
 
 
 - 
 - 
결론
- 바닐라 자바스크립트에서는 런타임 에러로 발생했을 많을 에러를 타입스크립트가 컴파일 타임에 검출할 수 있다.
 
 
 - 
 
코드 편집기 설정
- 
TSC
- TSC 자체도 타입스크립트로 구현된 명령행 도구이다. 이런 이유로 TSC는 자신을 컴파일하는 컴파일러라는 특별한 종류의 컴파일러가 된다.
 - TSC를 실행하려면 NodeJS가 필요하다.
 
 - 
ts-node
- ts-node를 설치하면, 
ts-node 타입스크립트파일명.ts명령어를 통해 타입스크립트를 컴파일하고 실행할 수 있다. 
 - ts-node를 설치하면, 
 
tsconfig.json
- 루트 디렉터리에 tsconfig.json이라는 파일이 존재해야 한다. 이 파일은 타입스크립트 프로젝트에서 어떤 파일을 컴파일하고, 어떤 자바스크립트 버전으로 방출하는지 등을 정의한다.
 tsc --init명령을 이용해 자동 설정할 수 있다.- 
옵션
include: TSC가 타입스크립트 파일을 찾을 디렉터리lib: TSC가 코드 실행 환경에서 이용할 수 있다고 가정하는 API(ex, ES5의 bind, Object.assign, DOM의 document.querySelector), 브라우저용 타입스크립트를 작성하기 위해 “dom”을 lib에 추가한다.moduleTSC가 코드를 컴파일할 대상 모듈 시스템(CommonJS, SystemJS, ES2015 등) 타입스크립트는 esnext 모듈 모드에서만 동적 임포트를 지원한다. {”module”: “esnext”}outDir생성된 자바스크립트 코드를 출력할 디렉터리strict유효하지 않은 코드를 확인할 때 가능한 엄격하게 검사noImplicitAny,strictBindCallApply,strictFunctionTypestargetTSC가 코드를 컴파일할 자바스크립트 버전(ES3, ES5, ES2015, ES2016 등)noImplicitAny암묵적인 any가 나타났을 때 예외를 일으킬 수 있다.strictNullChecksfalse로 설정하면, 이 때의 null은 never를 제외한 모든 타입의 하위 타입이다. 즉, 모든 타입은 null이 될 수 있으므로 모든 값이 null인지 아닌지를 먼저 확인해야 타입이 무엇인지 단정할 수 있는데, false로 설정하면 이 과정을 생략하게 된다. 그러나 예상치 않은 상황에서 값이 null이라면 런타임에 치명적인 널 포인터 예외가 발생한다. 컴파일 타임에 가능한 많은 버그를 검출하는 것이 목표라면 타입 시스템에서 null을 확인할 수 있어야 한다.preserveConstEnumsconst enum의 런타임 코드 생성을 활성화하려면 preserveConstEnums 설정을 true로 바꾼다.strictBindCallApplycall, apply, bind를 안전하게 사용하려면 이 플래그를 활성화해야 한다.noImplicitThis이 플래그를 true로 하면, 함수에서 항상 this 타입을 명시적으로 설정하도록 강제한다. 단, 클래스와 객체의 함수에는 this 지정을 강제하진 않는다.strictFunctionTypes호환성으로 인해, 타입스크립트 함수의 매개변수와 this 타입은 기본적으로 공변이다. 더 안전한 공변을 사용하려면 이 플래그를 true로 설정해야 한다.noImplicitReturns이 플래그를 활성화하면 함수 코드의 모든 경로에서 값을 반환하는지 확인할 수 있다. 추론되도록 하여 명시적 반환문을 되도록 적게 쓰는 사람도 있고, 타입 안전성을 향상시키고 타입 검사기가 더 많은 버그를 잡을 수 있다는 이유에서 반환문을 추가하는 사람도 있다keyofStringsOnly타입스크립트의 keyof는 기본적으로 number | string | symbol 타입의 값을 반환한다. 올바른 동작이지만, 이 때문에 타입스크립트에게 특정 키가 string이고 number나 symbol이 아니라는 사실을 증명해야 하는 귀찮은 상황에 놓일 수 있다. 따라서 타입스크립트가 string 키만 지원하던 예전처럼 동작하길 원한다면 이 플래그를 활성화한다.esModuleInterop이 옵션을 켜면 Commonjs방식으로 내보낸 모듈을 es모듈 방식의 import로 가져올 수 있게 해준다.- 
typeRoots기본적으로 node_modules/@types 디렉터리에서 서드 파티 타입 선언을 찾으며, 대부분은 이 동작을 바꿀 필요가 없다. 하지만 이 기본 동작을 오버라이드할 필요가 있다면,typeRoots에 타입 선언을 검색할 디렉터리들을 배열로 설정하면 된다.```tsx // /node_modules/@types 뿐만 아니라 typings 디렉터리에서도 타입 선언을 찾도록 설정 예시 "typeRoots" : [ "./typings", "./node_modules/@types" ] ``` - 
types: 타입스크립트가 어떤 패키지에서 타입을 검색할지 더 세밀하게 설정할 수 있다.// 리액트를 제외한 모든 서드 파티 타입 선언을 무시하는 설정 "types": ["react"] 
 
타입의 종류
- 
type이란, 값과 이 값으로 할 수 있는 일의 집합을 의미한다.
- ex) string 타입은 모든 문자열과 문자열에 수행할 수 있는 모든 연산, 문자열에 호출할 수 있는 모든 메서드의 집합이다.
 
 - 
any
- any는 모든 값의 집합이다. any를 사용하면 값이 자바스크립트처럼 동작하기 시작하면서 Type Checker가 작동하지 않는다. 사용하지 않는 것이 좋다.
 - 설정에서 
noImplicitAny플래그나strict플래그를 활성화하여, 암묵적인 any가 나타났을 때 예외를 일으킬 수 있다. 
 - 
unknown
- 타입을 미리 알 수 없는 어떤 값이 있을 때 any 대신 unknown을 사용한다. unknown의 타입을 검사해 정제하기 전까지는 타입스크립트가 unknown 타입의 값을 사용할 수 없게 강제한다.
 
 - 
boolean
- true, false
 
 - 
type literal
- 오직 하나의 값을 나타내는 타입
 - 예를 들어 
const c = true의 경우, const를 사용했으므로 타입스크립트는 그 변수의 값이 절대 변하지 않으리라는 사실을 알게 되어 해당 변수가 가질 수 있는 가장 좁은 값인 true로 추론한다. (let, var는 일반적인 타입으로만 추론한다) - type literal은 실수를 방지해 안전성을 추가로 확보해주는 강력한 언어 기능이다.
 
 - 
number
- 모든 숫자(정수, 소수, 양수, 음수, Infinity, NaN 등)의 집합이다.
 - 2의 53승까지의 정수를 표현할 수 있다.
 
 - 
bigint
- bigint는 자바스크립트와 타입스크립트에 새로 추가된 타입이다. 이를 이용하면 라운딩 관련 에러 걱정 없이 큰 정수를 처리할 수 있다.
 - 
2의 53승보다 더 큰 수도 표현할 수 있다.
let a = 1234n let f: bigint = 100n // bigint 
 - string
 - 
symbol
- ES2015에서 새로 추가된 기능이며, 실무에서는 심벌을 자주 사용하지 않는 편이다.
 - 객체와 맵에서 문자열 키를 대신하는 용도로 사용한다.
 
 - 
object
- 타입스크립트의 객체 타입은 객체의 형태를 정의한다.
 - 
타입스크립트가 객체의 형태를 추론하게 하거나 중괄호 안에서 명시적으로 타입을 묘사할 수 있다.
let a: { b: number } = { b: 12, } - 기본 타입(boolean, number, bigint, string, symbol)과 달리 객체를 const로 선언해도 타입스크립트는 더 좁은 타입으로 추론하지 않는다.
 - 객체 리터럴은 객체가 어떤 필드를 포함할 수 있는지 알고 있거나 객체의 모든 값이 같은 타입을 가질 때 사용한다.
 
 - 
인덱스 시그니처
- 
[key: T]: U와 같은 문법을 인덱스 시그니처라 부른다.- “이 객체에서 모든 T 타입의 키는 U 타입의 값을 갖는다”
 - 키 이름은 원하는 이름으로 바꿔도 된다.
 - 타입스크립트에 어떤 객체가 
여러 키를 가질 수 있음을 알려준다. 
let car: { [carName: string]: string } = { 'bmw': 'Marco', 'benz': 'John', 
 - 
 - 
Type Alias(타입 별칭)
type Age = number type Person = { name: string age: Age }- Type Alias로 타입을 가리킬 수 있다.
 - 타입스크립트는 Type Alias를 추론하지는 않으므로 반드시 Type Alias의 타입을 명시적으로 정의해야 한다.
 - 하나의 Type Alias를 두 번 정의할 수 없다.
 - Type Alias도 블록 Scope에 적용된다.
 
 - 
유니온 타입(합집합 |), 인터섹션 타입(교집합 &)
- 
실무에서는 대개 인터섹션보다 유니온을 자주 사용한다.
type Cat = { name: string; purs: boolean } type Dog = { name: string; barks: boolean; wags: boolean } type CatOrDogOrBoth = Cat | Dog // CatOrDogOrBoth에는 Cat, Dog 또는 둘 다 할당할 수 있다. type CatAndDog = Cat & Dog 
 - 
 - 
배열
T[]또는Array<T>라는 두 가지 배열 문법이 제공된다.
 - 
튜플
- 
튜플은 배열의 서브타입이다.
- 튜플은 길이가 고정되었고 각 인덱스의 타입이 알려져있다.
 
let a: [string, string, boolean] = ["marco", "jang", true] let b: [number, boolean, ...string[]] = [1, false, "a", "b", "c"] - 
읽기 전용 배열과 튜플
- 
readonly, Readonly, ReadonlyArray 유틸리티 중 골라서 사용한다.
type A = readonly string[] type B = ReadonlyArray<string> type C = Readonly<string[]> type D = readonly [number, string] type E = ReadOnly<[number, string]> 
 - 
 
 - 
 - 
null, undefined,
- 
null, undefined 값의 타입은 각각 null, undefined이다.
- null은 값이 없다는 의미다.
 - undefined는 아직 값이 정의되지 않았음을 의미한다.
 
 
 - 
 - 
void
- 
void는 명시적으로 아무것도 반환하지 않는 함수(ex, console.log)의 반환타입을 가리킨다.
- 즉, return문을 포함하지 않는 함수의 반환타입을 가리킨다.
 
 
 - 
 - 
never
- 
never는 절대 반환하지 않는 함수 타입을 가리킨다.
- 예를 들어, 예외(
throw)를 던지거나 영원히 실행(while(true))되는 함수가 절대 반환하지 않은 함수이다. 
 - 예를 들어, 예외(
 - never는 모든 타입의 서브타입이다. 즉 모든 타입에 never를 할당할 수 있다.
 
 - 
 - 
enum(열거형)
- 
enum은 해당 타입으로 사용할 수 있는 값을 열거하는 기법이다.
- 키를 값에 할당한다. 키가 컴파일 타임에 고정된 객체다.
 - 순서가 없는 자료구조다.
 - 타입스크립트는 자동으로 열거형의 각 멤버에 적절한 숫자를 추론하여 할당한다.
 - 
또는 개발자가 직접 값을 명시적으로 설정할 수도 있다(이런 습관이 적절)
enum Language { // 이름은 단수명사이며 첫 문자를 대문자로 하는 것이 관례다. English, // 키도 앞 글자를 대문자로 표시한다. Spanish, Russian, } enum Language { English = 0, Spanish = 1, Russian = 2, } 
 - 
const enum은 더 안전한 열거형 타입이며, 역방향 찾기를 지원하지 않는다.
- const enum 멤버는 문자열 리터럴로만 접근할 수 있다.
 - 
const enum 기본적으로 아무 자바스크립트 코드도 생성하지 않으며, 대신 필요한 곳에 열거형 멤버의 값을 채워 넣는다.
- 하지만 const enum을 사용할 때는 채워 넣기 기능을 되도록 피해야 하며, 직접 제어할 수 있는 타입스크립트 프로그램에서만 사용해야 한다. 왜냐하면 다른 사람이 const enum을 갱신하면 같은 열거형이 버번에 따라 다른 값을 갖게 되기 때문이다. npm으로 배포하거나 라이브러리로 제공할 프로그램에서는 const enum을 사용해서는 안 된다.
 
 
 - 열거형을 안전하게 사용하는 방법이 까다로우므로 열거형 자체를 사용하지 않는 편이 낫다.
 
 - 
 - 
Template Literal Type
 
함수
- 
선택적 매개변수
- 
선택적 매개변수는 필수 매개변수 보다 뒤에 추가되어야 한다.
function log(message: string, userId?: string) {...} 
 - 
 - 
기본 매개변수
- 매개변수를 선택적으로 만드는 것과 같다.
 - 
기본 매개변수는 어느 위치에나 추가할 수 있다.
function log(message: string, userId = 'Not signed In') {...} 
 - 
rest 매개변수
- 
인수를 여러 개 받는 함수라면 그 목록을 배열 형태로 건넬 수도 있다.
function sum(...numbers: number[]) {///} 
 - 
 - 
call, apply, bind
function add(a: number, b: number): number { return a + b } add(10, 20) add.apply(null, [10, 20]) add.call(null, 10, 20) add.bind(null, 10, 20)() - 
this
- 
함수에서 this를 사용할 때는 기대하는 this 타입을 함수의 첫 번째 매개변수로 선언해야 한다.
- 그러면 함수 안에 등장하는 모든 this가 의도한 this가 됨을 타입스크립트가 보장해준다.(함수 시그니처에서 사용한 this는 예약어이므로, 다른 매개변수와 완전히 다른 방식으로 처리된다)
 
// 런타임 에러 발생(bad) function getNextYear() { return this.getFullYear() + 1 } getNextYear.call(new Date()) // 2023 getNextYear() // 런타임 에러 Uncaught TypeError: this.getFullYear is not a function - 
타입스크립트에 정보를 제공한 덕분에, 런타임 에러 대신 컴파일 타임에 경고한다. (noImplicitThis 추천)
// 컴파일 타임 때 미리 에러 잡음(good) function getNextYear(this: Date) { //첫 번째 매개변수에 this 타입 선언 return this.getFullYear() + 1 } getNextYear.call(new Date()) // 2023 getNextYear() // 컴파일타임 에러 Uncaught SyntaxError: Unexpected token 'this' 
 - 
 
함수 호출 시그니처
- 
함수 호출 시그니처(Call 시그니처, Type 시그니처)는 타입스크립트의 함수 타입 문법이다.
- 값이 아닌 타입 정보(타입 수준 코드)만 포함한다. 따라서 기본값은 표현할 수 없다.
 - 
반환 타입을 명시해야 한다.
type Fn = (a: number, b: number) => number // 두 개의 number를 인수로 받아 한 개의 number를 반환하는 함수를 표현했다. 
 - 
단축형 호출 시그니처
type Log = (message: string, userId?: string) => void - 
전체 호출 시그니처
type Log = { (message: string, userId?: string): void }- 기능적으로 단축형과 동일하나 조금 더 복잡한 문법이므로, 오버로드된 함수와 같이 꼭 필요할 때만 전체 호출 시그니처를 사용한다.
 
 - 
오버로드된 함수
- 오버로드된 함수란, 호출 시그니처가 여러 개인 함수를 의미한다.
 - 
자바스크립트는 동적 언어이므로 어떤 함수를 호출하는 방법이 여러 가지다. 또한, 인수 입력 타입에 따라 반환 타입이 달라지기도 한다.
- 타입스크립트는 이러한 동적 특징을 오버로드된 함수 선언으로 제공하고, 입력 타입에 따라 달라지는 함수의 출력 타입은 정적 타입 시스템으로 각각 제공한다.
 
 - 
오버로드된 함수 시그니처를 이용하면 표현력 높은 API를 설계할 수 있다.
type Reserve = { (from: Date, to: Date, destination: string): Reservation (from: Date, destination: string): Reservation } let reserve: Reserve = ( from: Date, toOrDestination: Date | string, destination?: string ) => { if (toOrDestination instanceof Date && destination !== undefined) { // toOrDestination이 to임 // 왕복 여행 예약 } else if (typeof toOrDestination === "string") { // toOrDestination이 destination임 // 편도 여행 예약 } } - 오버로드 시그니처는 구체적(타입을 좁게)으로 유지하면, 함수를 구현하는 데 도움을 준다.
 - 
오버로드는 브라우저 DOM API에서 유용하게 활용된다.
type CreateElement = { (tag: "a"): HTMLAnchorElement (tag: "canvas"): HTMLCanvasElement (tag: string): HTMLElement } let createElement: CreateElement = (tag: string): HTMLElement => {} - 
함수의 프로퍼티를 만드는 데도 사용할 수 있다.
- 아래의 warnUser는 호출할 수 있는 함수인 동시에 boolean 속성인 wasCalled도 가지고 있다.
 
type WarnUser = { (warning: string): void wasCalled: boolean } const warnUser: WarnUser = warning => { if (warnUser.wasCalled) { return } warnUser.wasCalled = true alert(warning) } warnUser.wasCalled = false warnUser("hi") // alert창으로 hi 찍힘 console.log(warnUser.wasCalled) // true 
 
다형성
- 기대하는 타입을 정확하게 알고 있고, 실제 이 타입이 전달되었는지 확인할 때는 
구체 타입(concrete type)이 유용하다. - 
하지만 때로는 어떤 타입을 사용할지 미리 알 수 없는 상황이 있는데 이런 상황에서는 함수를 특정 타입으로 제한하기 어렵다.
type Filter = { (array: number[], f: (item: number) => boolean): number[] (array: object[], f: (item: object) => boolean): object[] } // 위 타입은 object가 객체의 실제 형태에 어떤 정보도 알려주지 않아 에러 발생- 이러한 경우에는 
제네릭 타입(generic type)을 사용한다. 
 - 이러한 경우에는 
 
제네릭 타입 매개변수 ( = 제네릭 타입)
- 여러 장소에 타입 수준의 제한을 적용할 때 사용하며, 
플레이스홀더 타입또는다형성 타입 매개변수라고 부른다. - 타입스크립트는 전달된 인자의 타입을 보고 T의 타입을 추론한다.
 - 꺾쇠괄호(<>)로 제네릭 타입 매개변수임을 선언한다.
 - T는 단지 타입 이름이다. T 대신 어떤 것도 사용할 수 있으나, 일반적으로 T, U, V, W 순으로 필요한 만큼 사용한다.
 - 
제네릭은 함수의 기능을 구체 타입을 사용할 때보다 더 일반화하여 설명할 수 있는 강력한 도구다.
- 가능하면 제네릭을 사용하자. 제네릭은 코드를 일반화하고, 재사용성을 높이고, 간결하게 유지하는 데 도움을 준다.
 
 - 
언제 제네릭 타입이 한정되는가?
- 
제네릭 타입의 선언 위치에 따라
타입의 범위와 제네릭 타입을 언제구체 타입으로 한정하는지도 결정된다.type Filter = { <T>(array: T[], f: (item: T) => boolean): T[] } const filter: Filter = (array, f) => array.filter(f) const names = [ { firstName: "marco" }, { firstName: "john" }, { firstName: "mary" }, ] // [런타임] 함수 호출 시 T가 {firstName: string)으로 한정됨 console.log(filter(names, item => item.firstName.startsWith("m"))) // [{"firstName": "marco"}, {"firstName": "mary"}] - 
위 예에서는
를 호출 시그니처의 일부로 선언했으므로 타입스크립트는 Filter 타입의 함수를 호출할 때구체 타입을 T로 한정한다.type Filter<T> = { (array: T[], f: (item: T) => boolean): T[] } // [컴파일 타임] Filter 타입의 함수를 선언할 때 T가 {firstName: string)으로 한정됨 const filter: Filter<{ firstName: string }> = (array, f) => array.filter(f) const names = [ { firstName: "marco" }, { firstName: "john" }, { firstName: "mary" }, ] console.log(filter(names, item => item.firstName.startsWith("m"))) // [{"firstName": "marco"}, {"firstName": "mary"}] - 위 예에서는 Filter 
Type Alias를 사용할 때타입을 명시적으로 한정함으로써 T의 범위를 Filter의Type Alias로 한정한다. - 
제네릭을 사용할 때란, 케이스마다 다음과 같은 시점을 의미한다.
- 함수에서는 함수를 호출할 때
 - 클래스라면 클래스를 인스턴스화 할 때
 - TypeAlias와 인터페이스에서는 이들을 사용하거나 구현할 때
 
 
 - 
 - 
제네릭 선언 위치(재정리)
- 
함수를 호출할 때 T를 구체 타입으로 한정하는 경우
// 전체 호출 시그니처 type Filter = { <T>(array: T[], f: (item: T) => boolean): T[] }; // 단축 호출 시그니처 type Filter = <T>(array: T[], f: (item: T) => boolean): T[] // 함수 선언 let filter: Filter = // ...// 각 함수 호출은 자신만의 T 한정 값을 갖게 되는 경우 function filter<T>(array: T[], f: (item: T) => boolean): T[] { //... } - 
함수를 선언할 때 T를 구체 타입으로 한정하는 경우
// 전체 호출 시그니처 type Filter<T> = { (array: T[], f: (item: T) => boolean): T[] }; // 단축 호출 시그니처 버전 type Filter<T> = (array: T[], f: (item: T) => boolean): T[] // 함수 선언 let filter: Filter<number> = // ... 
 - 
 - 
타입스크립트는
제네릭 함수의 인수에만 의지하여 제네릭 타입을 추론한다. 따라서 다음과 같은 경우 Promise의 제네릭 타입 인수를 명시하여, result의 타입을 정확히 추론하도록 한다.const promise = new Promise(resolve => resolve(45)) promise.then(result => console.log(result * 4)) // Object is of type 'unknown'.const promise = new Promise<number>(resolve => resolve(45)) promise.then(result => console.log(result * 4)) // 180 - 
특정 요소의 타입을 알 수 없는 때를 대비해 제네릭 타입에 기본값을 추가할 수 있다.
- 
기본 타입을 갖는 제네릭은 기본 타입을 갖지 않는 제네릭 뒤에 위치해야 한다.
type MyEvent<T = HTMLElement> = { target: T type: string }// T가 HTML 요소에 한정되도록 T에 경계 추가하고, 기본값도 추가 type MyEvent<T extends HTMLElement = HTMLElement> = { target: T type: string } 
 - 
 
타입 주도 개발
- 
type-driven development
- 
타입 시그니처(함수 호출 시그니처)를 먼저 정하고 값을 나중에 채우는 프로그래밍 방식
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[]; - 위와 같은 map 함수의 타입 시그니처를 통해, map이 어떤 동작을 하는지 감을 잡을 수 있다.
 
 - 
 - 
[Quiz] 타입스크립트는 함수 타입 시그니처에서 어떤 부분을 추론하는가? 매개변수 타입, 반환 타입, 또는 두 가지 모두?
- 함수 타입 시그니처(=함수 호출 시그니처)는 함수의 전체 타입을 표현하는 방법이다.
 - 함수 타입 시그니처는 타입 수준 코드만 포함하지만, 구현 코드와 거의 같다. 이는 언어 설계상 의도한 결정이다. 이렇게 함으로써 함수 타입 시그니처를 쉽게 추론할 수 있다.
 
 - 
[Quiz] 자바스크립트의 arguments 객체는 타입 안전성을 제공하는가? 그렇지 않다면 무엇으로 대체할 수 있을까?
- 
가변인자가 필요할 경우 arguments 객체를 사용할 수 있으나, arguments 객체는 안전하지 않다.
- arguments로 받은 인자는 any 타입으로 추론된다.
 - 또한 함수에서 매개변수를 받지 않도록 선언했으므로, arguments 객체를 사용한 함수를 호출하면 타입스크립트 입장에서는 인자를 받을 수 없다면서 TypeError를 발생시킨다.
 
 - 
따라서 arguments 객체 대신
rest parameters를 사용하는 것이 좋다. 왜냐하면rest parameters를 사용하면 가변 인자를 받으면 타입 안전성을 갖춘 함수가 되기 때문이다.function sum(...numbers: number[]): number { return Array.from(numbers).reduce((total, n) => total + n, 0) } console.log(sum(1, 2, 3)) 
 - 
 - 
[Quiz] reserve 함수에 명시적 시작 날짜 없이 목적지만 인수로 받는 세 번째 호출 시그니처를 추가한다.
type Reserve = { (from: Date, to: Date, destination: string): Reservation (from: Date, destination: string): Reservation (from: string): Reservation } const reserve: Reserve = ( fromOrDestination: Date | string, toOrDestination?: Date | string, destination?: string ) => { if (toOrDestination instanceof Date && destination !== undefined) { // 첫 번째 호출 시그니처를 지원함 // 왕복 여행 예약 } else if (typeof toOrDestination === "string") { // 두 번째 호출 시그니처를 지원함 // 편도 여행 예약 } else if (typeof fromOrDestination === "string") { // 세 번째 호출 시그니처를 지원함 // 목적지만 보고 편도 여행 예약 } } - 
[Quiz] call 함수에서 두 번째 인수가 string인 함수여야 정상 동작하도록 구현을 바꿔보자. 이를 제외한 모든 함수는 컴파일 타임에 에러를 발생시켜야 한다.
// before function fill(length: number, value: string): string[] { return Array.from({ length }, () => value) } function call<T extends unknown[], R>( f: (...args: T) => R, ...args: T // T는 [length, value] ): R { return f(...args) } let a = call(fill, 10, "a") // (f, length, value) console.log(a) // ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a"]// after function fill(value: string, length: number): string[] { return Array.from({ length }, () => value) } function call<T extends [string, number], R>( f: (...args: T) => R, ...args: T // T는 [length, value] ): R { return f(...args) } let a = call(fill, "a", 10) // (f, length, value) console.log(a) // ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a"] 
클래스와 인터페이스
- 타입스크립트의 클래스 기능 대부분은 
C#에서 빌려왔다. - 
타입스크립트 클래스를 컴파일하면 일반 자바스크립트 클래스가 된다.
- 프로퍼티 초기자와 데코레이터 같은 기능 일부는 자바스크립트에서도 지원되므로 실제 런타임 코드를 생성한다.
 - 반면, 가시성 접근자, 인터페이스, 제레닉 등은 타입스크립트 고유 기능이므로 컴파일 타임에만 존재한다.
 
 
인터페이스
- 인터페이스도 Type Alias처럼 타입에 이름을 지어주는 수단이다.
 - 
인터페이스와 Type Alias의 공통점
- 둘 다 
형태를 정의하며 두 형태 정의는 서로 할당할 수 있다. - 
Type Alias의
인터섹션(&)이 인터페이스의extends와 기능적으로 유사하다.type Food = { calories: number tasty: boolean } type Cake = Food & { sweet: boolean }interface Food { calories: number tasty: boolean } interface Cake extends Food { sweet: boolean }interface Food { calories: number tasty: boolean } type Cake = Food & { sweet: boolean }type Food = { calories: number tasty: boolean } interface Cake extends Food { sweet: boolean } 
 - 둘 다 
 - 
인터페이스와 Type Alias의 차이점 세 가지
- 
Type Alias 우측에는 타입 표현식을 포함한 모든 타입이 등장할 수 있다(Type Alias가 더 일반적임).
- 반면 인터페이스의 우측에는 반드시 형태로 작성되어야 한다.
 
type A = number type B = A | string // 타입 표현식 - 
인터페이스를 상속(extends)할 때 상속받는 인터페이스의 타입에 상위 인터페이스를 할당할 수 없으면 컴파일 타임 에러를 발생시킨다.
- 반면, Type Alias와 인터섹션(&)으로 바꾸면 확장하는 타입을 최대한 조합하는 방향으로 동작하여, 컴파일 타임 에러가 발생하지 않는다.
 
interface A { good(x: number): string bad(x: number): string } interface B extends A { good(x: string | number): string bad(x: string): string // Interface 'B' incorrectly extends interface 'A'. }interface A { good(x: number): string bad(x: number): string } type B = A & { good(x: string | number): string bad(x: string): string } // 컴파일 타임 에러 나지 않음 - 
이름과 범위가 같은 인터페이스가 여러 개 있으면 이들이 자동으로 합쳐진다(선언병합).
- 반면 Type Alias에 대해 선언 병합을 시도하면 컴파일 타임 에러가 발생한다.
 
 
 - 
 
고급 타입
- 타입스크립트의 기본 타입 문법만으로 다양한 데이터의 타입을 표현하기 어려워졌다. 타입에 관한 타입인 
메타 타입( 고급 타입) 이 필요해졌다. 즉, 메타 타입은 추상화된 타입이다. 
타입 간의 관계
- 
서브타입
- 
B가 A의 서브타입이면, A가 필요한 곳에는 어디든 B를 안전하게 사용할 수 있다.(B가 A에 포함된다)
- ex) 배열은 객체의 서브타입이다.
 
 A >: B는 B는 A와 같거나 A의 서브타입이라는 의미다.(공식 문법은 아님)
 - 
 - 
슈퍼 타입
- 
B가 A의 슈퍼타입이면, B가 필요한 곳에는 어디든 A를 안전하게 사용할 수 있다.(A가 B에 포함된다)
- ex) 객체는 배열의 슈퍼타입이다.
 
 A <: B는 B는 A와 같거나 A의 슈퍼타입이라는 의미다.(공식 문법은 아님)
 - 
 - 
타입스크립트는 어떤 형태를 요구할 때 건넬 수 있는 타입은, 요구되는 타입에 포함된 프로퍼티 각각에 대해
<: 기대하는 타입인 프로퍼티들을 가지고 있어야 한다.- 타입과 관련해 타입스크립트 
형태는 그들의프로퍼티 타입에공변한다고 말한다. 
 - 타입과 관련해 타입스크립트 
 - 
공변의 가변성 네 종류
- 
불변(invariance)
- 정확히 T를 원함
 
 - 
공변(covariance)
- 
<:T를 원함 (나보다 더 큰 집합(슈퍼 타입)인 T를 원한다)- 타입스크립트에서 모든 복합타입의 멤버(객체, 클래스, 배열, 함수, 반환 타입)는 공변이다.
 
 
 - 
 - 
반변(contravariance)
- 
>:T를 원함 (나보다 더 작은 집합(서브 타입)인 T를 원한다)- 함수 매개변수 타입만 예외적으로 반변이다.
 
 
 - 
 - 
양변(bivariance)
<:T또는>:T를 원함
 
 - 
 - 
함수 가변성
- 함수 A가 함수 B와 같거나, 적은 수의 매개변수를 가지며 다음을 만족하면, 
A는 B의 서브타입이다. 1. A의 this 타입을 따로 지정하지 않으면 ‘A의 this 타입>:B의 this 타입’이다. 2. ‘A의 각 매개변수>:B의 대응 매개변수’이다. - 함수의 매개변수 타입은반변이다. 즉, 함수를 다른 함수에 할당하려면 ‘this를 포함한 매개변수 타입>:할당하려는 함수의 대응 매개변수 타입’ 조건을 만족해야 한다. 3. ‘A의 반환 타입<:B의 반환 타입’이다. - 함수의 반환 타입은공변이다. 즉, 함수가 다른 함수의 서브타입이라면 ‘서브타입 함수의 반환 타입<:다른 함수의 반환 타입’을 만족해야 한다. - 안전한 공변을 사용하려면 
strictFunctionTypes플래그를 설정해야 한다. 
 - 함수 A가 함수 B와 같거나, 적은 수의 매개변수를 가지며 다음을 만족하면, 
 
const 타입으로 좁히기
- 
변수 선언 키워드
- let, var로 선언한 변수는 그 변수의 타입이 기본 타입으로 넓혀진다.
 - const로 선언하면 그 변수의 타입이 리터럴 값으로 좁혀진다
 
 - 
const 타입
- 
TS에서 타입이 넓혀지지 않도록 하는
const라는 특별 타입이 제공된다. 이를 Type assertion으로 활용한다. 멤버들까지 자동으로 readonly가 되며, 중첩된 자료구조에도 재귀적으로 적용된다.let c = [1, { x: 2 }] as const // readonly [1, {readonly x: 2}] 
 - 
 
정제
- 
discriminated union type
- 
유니온의 멤버가 서로 중복될 수 있으므로 타입스크립트는 유니온의 어떤 타입에 해당하는지를 조금 더 안정적으로 파악할 수 있어야 한다.
- 리터럴 타입을 이용해 유니온 타입이 만들어낼 수 있는 각각의 경우를 tag하는 방식으로 이 문제를 해결할 수 있다.
 
 - 
좋은 tag의 조건
- 리터럴 타입이다.
 - 제네릭이 아니다(태그는 제네릭 타입 인수를 받지 않아야 함).
 - 상호 배타적이다(유니온 타입 내에서 고유함).
 
 - 
유니온 타입의 다양한 경우를 처리하는 함수(플럭스, 리덕스 리듀서, 리액트의 useReducer)를 구현해야 하는 경우 태그된 유니온을 사용하는 것이 좋다.
type UserTextEvent = { type: "TextEvent" target: HTMLInputElement } type UserMouseEvent = { type: "MouseEvent" target: HTMLElement } type UserEvent = UserTextEvent | UserMouseEvent function handle(event: UserEvent) { if (event.type === "TextEvent") { // UserTextEvent return } // UserMouseEvent } 
 - 
 
고급 객체 타입
- 
keyin 연산자
- 
대괄호 표기법을 사용하는 keyin으로 프로퍼티 타입을 찾아 편하게 타입을 추출할 수 있다.
type FriendList = APIResponse["user"]["friendList"] // 친구 목록 객체 타입 얻기 type FriendList = FriendList["friends"][number] // 친구 한 명의 타입 얻기 
 - 
 - 
keyof 연산자
- 
keyof을 이용하여 객체의 모든 키를 문자열 리터럴 타입 유니온으로 얻을 수 있다.
type FriendList = keyof APIResponse["user"]["friendList"] // 'count' | 'friends' 
 - 
 - 
Record 타입
- 
Record 타입을 이용하면 무언가를 매핑하는 용도로 객체를 활용할 수 있다.
type Weekday = "MON" | "TUE" | "WED" | "THU" | "FRI" type Day = Weekday | "SAT" | "SUN" // Record를 이용하면 nextDay의 키와 값에 제한을 추가할 수 있다. let nextDay: Record<Weekday, Day> = { MON: "TUE", TUE: "WED", WED: "THU", THU: "FRI", FRI: "SAT", SUN: "MON", // Object literal may only specify known properties, and 'SUN' does not exist in type 'Record<Weekday, Day>' } 
 - 
 - 
mapped type
- 
mapped type은 타입스크립트의 고유한 언어 기능이며, 고유 문법이 있다.
type MyMappedType = { [key in UnionType]: ValueType } - 
Record 타입을 구현하는데 , mapped type이 사용됐다.
type Record<K extends keyof any, T> = { [P in K]: T }type Weekday = "MON" | "TUE" | "WED" | "THU" | "FRI" type Day = Weekday | "SAT" | "SUN" let nextDay: { [K in Weekday]: Day } = { MON: "TUE", TUE: "WED", WED: "THU", THU: "FRI", FRI: "SAT", SUN: "MON", // Object literal may only specify known properties, and 'SUN' does not exist in type '{ MON: Day; TUE: Day; WED: Day; THU: Day; FRI: Day; }' } - 
mapped Type을 활용한 타입스크립트의 내장 타입
- 
Record<Keys, Values>- Keys 타입의 키와 Values 타입의 값을 갖는 객체
 
 - 
Partial<Object>- Object의 모든 필드를 선택형으로 표시
 
 - 
Required<Object>- Object의 모든 필드를 필수형으로 표시
 
 - 
Readonly<Object>- Object의 모든 필드를 읽기 전용으로 표시
 
 - 
Pick<Object, Keys>- 주어진 Keys에 대응하는 Object의 서브타입을 반환
 
 
 - 
 
 - 
 - 
컴패니언 객체 패턴
- 타입과 값(객체)을 쌍으로 묶는 패턴이다.
 - 타입스크립트에서 타입과 값은 서로 별도의 네임스페이스를 갖는다. 따라서 같은 영역에 하나의 이름을 타입과 값 모두에 연결할 수 있다.
 - 이 패턴을 이용하면 타입과 값을 한 번에 import할 수 있다.
 - 타입과 객체가 의미상 관련되어 있고, 이 객체가 타입을 활용하는 메서드를 제공하면 이 패턴을 이용하면 좋다.