식별자 영역 / 타입 구현 영역


type interface 키워드를 사용한 타입 선언은 = 등호와 {} 중괄호를 기준으로 식별자(좌), 타입 구현(우) 영역으로 구분할 수 있다(아래 코드 기준 파란색은 식별자 영역, 빨간색은 타입 구현 영역).

// type 식별자 = 타입 구현
type MyType = string | number;

// interface 식별자 { 타입 구현 }
interface User { name: string; age: number; }

제약 조건 | Constraints


식별자 영역에서 사용하는 extends는 제약 조건을 추가할 수 있다. 기본적으로 제네릭엔 모든 타입이 올 수 있지만 제약 조건을 이용해 특정 타입으로 제한할 수 있다.

기본 문법 : T extends UTU의 부분 집합(T ⊆ U)으로 볼 수 있음

ex) 64 extends number : O — 64number의 부분 집합이므로 ex) number extends 64 : X — 64보다 number가 더 큰 개념이므로

// string, number만 받도록 제네릭(T) 타입 제약
interface User<T extends string | number> {
  name: string;
  age: T;
}

const userA: User<number> = { name: 'Smith', age: 32, }; // OK
const userA: User<string> = { name: 'Smith', age: '32', }; // OK

제약 조건을 이용해 제네릭 타입이 배열임을 알려줄 수 있다. 제약 조건을 사용하지 않으면 제네릭 타입(T)이 배열인지 타입스크립트는 알지 못하기 때문에 ...T 같은 Rest 문법을 사용하면 에러가 발생한다.

// 타입 시스템에 Array.push를 구현한 예시
type Push1<T, U> = [...T, U]; // Error! A rest element type must be an array type

type Push2<T extends unknown[], U> = [...T, U];
type Result = Push2<[1, 2], '3'>; // [1, 2, '3']

제네릭 타입에 length 같은 특정 프로퍼티가 있다는 걸 알려줄 때도 제약 조건을 사용한다. 제약 조건 없이 사용하면 제네릭 타입에 length 프로퍼티가 있는지 타입스크립트가 알지 못하기 때문에 에러가 발생한다.

// 주어진 배열의 길이를 반환하는 제네릭 Length
type Length1<T> = T['length']; // Error! Type ‘length’ cannot be used to index type ‘T’

type Length2<T extends { length: number }> = T['length'];
type T0 = Length2<[1, 2, 3]>; // 3

// T를 배열로 지정해도 length 프로퍼티를 사용할 수 있다
type Length3<T extends unknown[]> = T['length'];
type T1 = Length3<[1, 2]>; // 2

<aside> 💡 타입 매개변수에도 = 등호를 사용해 기본값을 지정할 수 있다. 제네릭을 선택적으로 받는 상황에서 사용한다. 아래는 K가 주어지지 않은 경우를 대비해 K의 기본값을 keyof T로 지정한 예시(참고). ex) type MyType<T, K extends keyof T = keyof T> = T & { ... }

</aside>

조건부 타입 | Conditional Types


타입 구현 영역에서 사용하는 extends는 삼항 연산자를 이용해 조건부 타입을 추가할 수 있다. 제네릭이 가지는 타입 종류에 따라(조건에 따라) 다른 값을 지정할 때 유용하다.

기본 문법 : T extends U ? X : YTU에 할당 가능하면(부분 집합이면) X, 아니면 Y 리턴

<aside> 💡 T가 유니온일 때 T extends U 구조를 사용하면, 유니온 T의 각 요소들을 순회하면서 조건 적용

</aside>