Picture of the author
Published on

Typescript Type의 특성

Authors
  • avatar
    Name
    김병규
    Twitter

타입스크립트 타입을 지정하면서 깨달았던 점들을 정리해둡니다.

타입을 정의할 때 extends는 조건문처럼 활용됩니다.

type Foo = 1 extends number ? true : false // true
type Bar = 'a' extends boolean ? true : false // false

extends 조건문은 마치 삼항 연산자의 문법을 띱니다. 이는 타입에 제너릭이 포함되어 있을 때 유용합니다.

type IsNumber<T> = T extends number ? true : false
type Foo = IsNumber<3> // true

type ToNumber<T> = T extends any[] ? T[number] : T
type A = ToNumber<3> // 3
type B = ToNumber<[3]> // 3

extends 앞의 타입이 유니온일 경우, 분배법칙이 작용합니다.

type PassOnlyNumber<T> = T extends number ? T : never
type A = PassOnlyNumber<number | string | boolean> // number

extends 앞에 유니온 타입이 올 경우, 유니온은 분배법칙이 적용됩니다. 위의 경우에는

// -> (number extends number ? number : never) | (string extends number ? string : never) | (boolean extends number ? boolean : never)
// -> (number) | (never) | (never)
// -> number

이런 과정을 거쳐서 number 타입이 정제됩니다. never는 최종 타입에서 무시됩니다.

분배가 된다는 규칙을 활용하여 순열 타입도 만들 수 있습니다.

type Permutation<T, U = T> = [U] extends [never]
  ? []
  : U extends T
  ? [U, ...Permutation<Exclude<T, U>>]
  : []
type A = Permutation<'A' | 'B' | 'C'>
// ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']

infer 유틸을 통해 타입을 변수처럼 활용이 가능합니다.

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never

(...args: any[]) => infer R 라는 형태에서 infer R는 함수의 반환타입에 위치해 있습니다. 이처럼 extends 뒤에 나와야하는 타입 위치에 infer 변수로 지정하면, 해당 타입을 캐치할수 있습니다. 이후에 extends 값이 참인 부분에 해당 변수를 이용할 수 있으며, 경우에는 R를 그대로 반환합니다. 즉 함수의 반환타입을 뱉어줍니다.

['length']는 튜플에서만 유효합니다.

type StringLength = 'abcd'['length'] // number
type ArrayLength = number[]['length'] // number
type TupleLength = [3, 2, 1]['length'] // 3

타입에서 [] 인덱스 연산자는 배열, 객체 등에 사용할 수 있습니다. 그 중 ['length']는 객체의 'length'의 키값을 반환합니다.

배열, 튜플도 객체의 일부이기 때문에 이런 접근이 가능합니다. 튜플은 특수하게 타입이 Narrowing되어 상수 값이 반환됩니다.

이를 응용하여 튜플의 마지막 원소 타입을 추론하는 것이 가능합니다.

type Last<T extends any[]> = [any, ...T]T['length']]

type A = [4,3,2,1]
type B = ['a','b','c','d']

type Foo = Last<A> // 1
type Bar = Last<B> // 'd'

그렇다면 string의 length는 어떻게 구할 수 있을까요?

이는 튜플로 타입 변환후 ['length']를 통해 구할 수 있습니다.

type StringToArray<S extends string> = S extends `${infer Left}${infer Rest}`
  ? [Left, ...StringToArray<Rest>]
  : []
type LengthOfString<S extends string> = StringToArray<S>['length']

type Foo = LengthOfString<'abcde'> // 5

References.