type
interface
키워드를 사용한 타입 선언은 =
등호와 {}
중괄호를 기준으로 식별자(좌), 타입 구현(우) 영역으로 구분할 수 있다(아래 코드 기준 파란색은 식별자 영역, 빨간색은 타입 구현 영역).
// type 식별자 = 타입 구현
type MyType = string | number;
// interface 식별자 { 타입 구현 }
interface User { name: string; age: number; }
식별자 영역에서 사용하는 extends
는 제약 조건을 추가할 수 있다. 기본적으로 제네릭엔 모든 타입이 올 수 있지만 제약 조건을 이용해 특정 타입으로 제한할 수 있다.
기본 문법 :
T extends U
—T
는U
의 부분 집합(T ⊆ U
)으로 볼 수 있음ex)
64 extends number
: O —64
는number
의 부분 집합이므로 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>
타입 구현 영역에서 사용하는 extends
는 삼항 연산자를 이용해 조건부 타입을 추가할 수 있다. 제네릭이 가지는 타입 종류에 따라(조건에 따라) 다른 값을 지정할 때 유용하다.
기본 문법 :
T extends U ? X : Y
—T
가U
에 할당 가능하면(부분 집합이면)X
, 아니면Y
리턴
<aside>
💡 T
가 유니온일 때 T extends U
구조를 사용하면, 유니온 T
의 각 요소들을 순회하면서 조건 적용
</aside>