221021 TIL | 이펙티브 타입스크립트_#2 타입스크립트의 타입 시스템

아이템7~아이템 8까지 정리

타입스크립트 설치 시 할 수 있는 것

  • 타입스크립트 컴파일러(tsc)
  • 단독으로 실행할 수 있는 타입스크립트 서버(tsserver)

타입스크립트 서버는 언어 서비스를 제공한다.

  • 언어 서비스는 코드 자동완성, 사양 검사, 검색, 리팩터링을 포함한다.

타입 스크립트가 어떻게 동작하는지 알고 싶다면 편집기의 기능을 이용하여 타입 선언 파일을 찾아보자.

타입 설정

타입은 값들의 집합이다.

  • 숫자 42와 37.25는 number 타입, ‘Canada’는 string 타입, null과 undefined는 stricNullCheck 여부에 따라 number일수도 아닐수도 있다.
  • 가장 작은 집합은 아무것도 포함하지 않는 공집합이며, 타입스크립트에서는 never 타입으로 선언된 변수이다.
  • 다음으로 작은 집합은 유닛(unit)이라고 불리는 리터럴 타입이다. 한가지 값만 포함한다.
    • ex ) type A = 'A' or type B = 'B'
  • 두개 혹은 세 개로 묶으려면 union 타입 사용
    • ex ) type AB = ‘A’ | ‘B’ or type AB12 = ‘A’ | ‘B’ | 12;

& 연산자와 인터섹션 타입

interface Person {
  name: string;
}
interface Lifespan {
  birth: Date;
  death?: Date;
}
type PersonSpan = Person & Lifespan;
  • 두 타입의 인터섹션(intersection, 교집합)을 계산한다.
  • 타입 연산자는 인터페이스의 속성이 아닌 값의 집합을 적용
    • 타입의 범위가 Person 범위에 있냐~ Lifespan 범위에 있냐~
  • 때문에 PersonSpan은 Person, Lifespan 두 속성을 같이 가지는 인터섹션 타입에 속한다.
    • 인터섹션 타입은 각 타입 내의 속성을 모두 포함하는 게 일반적이다.
type PersonSpan = Person & Lifespan;
const ps: PersonSpan = {
  name: 'Alan Turing',
  birth: new Date('1912/06/23'),
  death: new Date('1954/06/07'),
};  // 정상

인터페이스의 유니온 타입

interface Identified {
  id: string;
}
interface Person {
  name: string;
}
interface Lifespan {
  birth: Date;
  death?: Date;
}
type K = keyof (Person | Lifespan);  // Type is never
  • 인터페이스의 유니온 타입은 정반대로 작동한다.
    • keyof (A&B) = (keyof A) | (keyof B)
    • keyof (A|B) = (keyof A) & (keyof B)
  • Person과 Lifespan

keyof란?

Object key들의 리터럴 값들을 가져온다.

console.log(keyof Person); // name
console.log(keyof Lifespan); // birth, death

⭐ 타입 확장할 때 자주 사용하는 방법

  • extensds 키워드를 주로 사용한다.
  • 이 때 PersonSpan을 Person의 서브 타입이라고 한다.
    • 어떤 집합이 다른 집합의 부분 집합일 때 서브 타입이라고 한다.
interface Person {
  name: string;
}
interface PersonSpan extends Person {
  birth: Date;
  death?: Date;
}

제너릭 한정자로 쓰이는 extends 키워드

  • 타입을 확장할 때도 쓰이지만, 제너릭 타입에서 한정자로도 쓰인다.
  • K의 부분 집합 string 처럼 쓰인다.
 function getKey<K extends string>(val: any, key: K) {
  // ...
}

예시 )

getKey({}, 'x');  // 정상. 'x'는 string을 상속한다.
getKey({}, Math.random() < 0.5 ? 'a' : 'b');  // 정상, 'a'|'b'는 string을 상속한다.
getKey({}, document.title);  // 정상, string은 string을 상속한다.
getKey({}, 12);
        // ~~ Type '12' is not assignable to parameter of type 'string'

타입이 집합이라는 관점은 배열과 튜플의 관계를 명확하게 해준다.

const list = [1, 2];  // Type is number[]
const tuple: [number, number] = list;
   // ~~~~~ Type 'number[]' is missing the following
   //       properties from type '[number, number]': 0, 1
  • number[]는 [number, number]의 부분집합이 아니기 때문에 오류가 발생한다.

  • 타입스크립트는 쌍을 비교할 때 length를 비교한다.

const triple: [number, number, number] = [1, 2, 3];
const double: [number, number] = triple;
   // ~~~~~~ '[number, number, number]' is not assignable to '[number, number]'
   //          Types of property 'length' are incompatible
   //          Type '3' is not assignable to type '2'

요약

타입스크립트 용어 집합 용어
never 공집합
리터럴 타입 원소가 1개인 집합
값이 T에 할당 가능 값 ∈ T (값이 T의 원소)
T1이 T2에 할당 가능 T1 ⊆ T2
T1이 T2를 상속 T1 ⊆ T2
T1 | T2 (T1과 T2의 유니온) T1 ∪ T2
T1 & T2 (T1과 T2의 인터섹션) T1 ∩ T2
unknown 전체(universal) 집합

출처

  • 이펙티브 타입스크립트