배열의 요소를 비구조화하여 타입검사하기

이현재·2023년 10월 10일
6

문제

  • 배열의 각 요소가 undefined가 아닌지 확인하기 위한 함수가 필요했다.
  • nullish coalescing operator(??)는 undefinednull을 모두 필터링한다.
  • Array.find method는 올바른 타입 추론이 되지않았다.

해결

  • variadic tuple 타입을 사용한 튜플의 비구조화
  • 재귀 타입을 활용하여 배열 각 요소의 타입을 추론

내용

아래는 위 내용을 해결한 함수와 타입이다.
firstNotUndefined 함수는 rest parameter로 받는 요소 중 undefined가 아닌 첫번째 요소를 정확한 타입의 추론과 함께 반환한다.

const firstNotUndefined = <Tuple extends unknown[]>(
  ...array: Tuple
): UndefinedCoalescing<Tuple> => array.find((item) => item !== undefined);

UndefinedCoalescing 타입은 다음의 과정으로 동작한다.

  1. Tuple 모든 아이템이 undefined가 속한 union 타입이라면 모든 아이템의 유니온 타입을 반환한다.
  2. 각 아이템 중 하나라도 undefined를 갖지 않은 아이템이 있다면 undefined를 제외한 모든 아이템의 유니온 타입을 반환한다.
type Foo = UndefinedCoalescing<[number | undefined, string | undefined]>
// type Foo = string | number | undefined

type Bar = UndefinedCoalescing<[number, string | undefined]>
// type Bar = number | string

type Baz = UndefinedCoalescing<[number, string]>
// type Baz = number | string

UndefinedCoalescing

UndefinedCoalescingNullish Coalescing에서 따온 이름이다. 이 유틸타입을 조금 더 자세히 들여다보자.

export type UndefinedCoalescing<Tuple extends unknown[]> = TupleToUnion<
  // Tuple의 모든 요소가 undefined를 갖는다면, Tuple의 유니온 타입으로 반환
  EveryHas<Tuple, undefined> extends true
    ? Tuple
  // 그렇지 않다면 각 요소에서 undefined를 제외한 튜플의 유니온 타입으로 반환
    : EveryExclude<Tuple, undefined>
>;

EveryHas

EveryHas는 튜플에서 모든 요소가 condition을 갖는지 재귀를 통해 확인해준다.

type EveryHas<Tuple extends unknown[], Condition> = Tuple extends [
  infer FirstItem,
  ...infer Rest
]
  ? ItHas<FirstItem, Condition> extends false
    ? false
    : EveryHas<Rest, Condition>
  : true;

ItHas

ItHas는 Condition이 Item에 포함되어있는지 확인해준다.
대괄호로 Condition, Item을 감싸준 이유는 타입의 분산성을 방지하여 사용하기 위함이다.
distributive-conditional-types

type ItHas<Item, Condition> = [Condition] extends [Item] ? true : false;

EveryExclude

EveryExclude는 재귀를 통해 튜플의 각 요소에서 Condition을 제외한 타입의 튜플로 바꿔준다.

type EveryExclude<Tuple extends unknown[], Condition> = Tuple extends [
  infer FirstItem,
  ...infer Rest
]
  ? [
      ItHas<FirstItem, Condition> extends true
        ? Exclude<FirstItem, Condition>
        : FirstItem,
      ...EveryExclude<Rest, Condition>
    ]
  : [];

마무리

튜플의 비구조화, 재귀를 통해 타입안정성을 더한 코딩을 할수있었다.
튜플의 비구조화는 타입스크립트 4.0에 새롭게 추가되었다. 그 이전에는 좀 더 복잡한 방식으로 구현되었으며 그 내용은 참고 블로그에 자세하게 작성되었으며 꽤 재밌으니 참고.


참고

https://blog.cometkim.kr/posts/typescript-tuples/
https://devblogs.microsoft.com/typescript/announcing-typescript-4-0-beta/#variadic-tuple-types
https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types

profile
코드 보는걸 좋아합니다. 궁금한게 많습니다.

0개의 댓글