구보현 블로그

[TypeScript]Type Challenges - Awaited, If, Concat

20220417

Type challenges를 풀이한 내용을 정리했습니다.

Awaited

이렇게 Promise<ExampleType> 처럼 Promise와 같이 래핑된 타입이 있는 경우, 래핑된 타입 내부에 있는 타입을 어떻게 가져올 수 있을까?

type MyAwaited = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type X = Promise<string>
type Y = Promise<{ field: number }>
type Z = Promise<Promise<string | number>>

type cases = [
  Expect<Equal<MyAwaited<X>, string>>,
  Expect<Equal<MyAwaited<Y>, { field: number }>>,
  Expect<Equal<MyAwaited<Z>, string | number>>,
]

// @ts-expect-error
type error = MyAwaited<number>

solution

type MyAwaited<T extends Promise<any>> = T extends Promise<infer K> ? K : never
  • 일단 Promise만 제네릭 타입으로 받아야 하므로 T extends Promise<any> Promise 로 한정해준다.
  • 제약 조건에 따라 값을 받은 다음에 Promise 안에 있는 값을 찾는다.
  • infer

If

조건 C를 받아서 truthy면 T 타입 리턴, falsy면 F 반환한다. C는 true나 false 타입이어야 한다.

type If<C, T, F> = any


/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<If<true, 'a', 'b'>, 'a'>>,
  Expect<Equal<If<false, 'a', 2>, 2>>,
]

// @ts-expect-error
type error = If<null, 'a', 'b'>

solution

type If<C extends boolean, T, F> = C extends true ? T : F;
  • C 타입은 boolean으로 제한하고
  • C가 true이면 T 타입, 아니면 F 타입 반환.

Concat

type system에서 자바스크립트의 Array.concat을 구현한다.

type Concat<T, U> = any


/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<Concat<[], []>, []>>,
  Expect<Equal<Concat<[], [1]>, [1]>>,
  Expect<Equal<Concat<[1, 2], [3, 4]>, [1, 2, 3, 4]>>,
  Expect<Equal<Concat<['1', 2, '3'], [false, boolean, '4']>, ['1', 2, '3', false, boolean, '4']>>,
]

solution

type Concat<T extends any[], U extends any[]> = [...T, ...U]
  • T와 U타입을 array 타입으로 제한
  • spread operator를 사용

Infer?

infer에 대해서 더 알아보자.

  • infer 키워드는 조건부 타입을 보완하며 extends 절 외부에서 사용할 수 없다.
  • infer를 사용하면 제약 조건 내에서 참조하거나 반환할 변수를 정의할 수 있다.

타입스크립트의 내장 타입인 ReturnType을 보면, function 타입을 받아서 리턴 타입을 준다.

type a = ReturnType<() => void> // void
type b = ReturnType<() => string | number> //string | number 
type c = ReturnType<() => any> // any
  • ReturnType은 먼저 type 인수가 Function 타입으로 제한한다.
  • 그리고 체크하는 과정에서 반환 타입을 변수로 만들고 infer R 하고 성공하면 반환한다.
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

infer use cases

  • infer 키워드는 unwrapping type으로 설명된다.

  • function의 첫 번째 인자

type GetFirstArgumentOfAnyFunction<T> = T extends (
  first: infer FirstArgument, 
  ...args: any[]
) => any 
  ? FirstArgument 
  : never

type T = GetFirstArgumentOfAnyFunction<(name: string, age: number) => void> // string 
  • array type
type ArrayType<T> = T extends (infer Item)[] ? Item : T

type T = ArrayType<[string, number]> // string | number 

infer 키워드는 third-party 타입스크립트 코드를 사용할 때 타입을 unwrap하고, 저장할 수 있는 강력한 도구다.

참고 자료

https://blog.logrocket.com/understanding-infer-typescript/