본문 바로가기
개발/JS

[Typescript] array, object 를 literal 타입으로 변환하기

by JeonJaewon 2021. 9. 25.

 타입스크립트를 사용하다보면 literal 타입이 유용한 경우가 많습니다. 

이번 포스팅에서는 제가 타입스크립트 개발을 하면서 필요성을 느꼈던 리터럴 타입 변환에 대해서 다루어 보겠습니다.


명확하지 않은 배열의 타입

 제가 좋아하는 야구를 예로 들어보겠습니다.

야구의 외야에는 세 개의 포지션이 있고, 위치에 따라서 익수, 견수, 익수라고 합니다.

 

const outfielders = ['left', 'center', 'right'];

// 타입 추론 결과: const outfielders: string[]

 

모든 외야수 문자열을 포함하는 outfielders 배열 하나를 선언했습니다.

이렇게 작성하면 이 배열의 타입은 string[]으로 추론됩니다.

우리의 목적은 외야수 배열을 표현하고 싶었는데, 이렇게 추론이 된다면 어떠한 문자열이든 포함할 수 있는 배열로 타입 추론이 이루어졌네요. 어떻게하면 더 타입이 명확해질까요?

 


Const Assertion

const outfielders = ['left', 'center', 'right'] as const;

// 타입 추론 결과: const outfielders: readonly ["left", "center", "right"]

 

배열의 선언 뒤에 as const 를 추가했습니다. (const assertion)

 이렇게 하면 주석 처리된 문구처럼 outfielders 배열의 타입 추론 범위를 string에서 특정 값으로 좁힐 수 있습니다.

 그러나 만약 우리가 이 배열의 요소들을 타입으로서 재활용하고 싶다면 어떻게 하면 좋을까요?

 

즉  'left' | 'center' | 'right' 타입을 얻고 싶다면요.

물론 따로 타입을 생성해도 되지만 코드가 중복될 것입니다.

 


typeof  연산자와 Generic

const outfielders = ['left', 'center', 'right'] as const;
type Outfielder = typeof outfielders[number];

// type Outfielder = "left" | "center" | "right"

 

typeof 연산자를 활용하여 선언이 가능합니다. 따로 타입을 작성하는 것 보다 훨씬 좋아보입니다. 이러한 형태를 generic으로 재사용할 수 있게 해 봅시다.

 

type ArrayLiteral<T extends ReadonlyArray<any>> = T[number];

 

const assertion을 사용하면 reaonly 값이 선언되기 때문에 ReadonlyArray를 제약 조건으로 추가했습니다.

 


객체의 value 값을 타이핑하고 싶다면?

const outfielders = {
  left: 'left',
  center: 'center',
  right: 'right',
} as const
type ObjectLiteral<T extends { [i: string]: any }> = T[keyof T];
type Outfielder = ObjectLiteral<typeof outfielders>;
// type Outfielder = "left" | "center" | "right"

 

이런식으로 작성하면 enum과 동일한 구현이 가능합니다.
enum은 babel에 의해서 즉시 실행 함수(IIFE)로 구현이 되고, 트랜스파일러에 의해서 트리 쉐이킹이 되지 않습니다.
트리 쉐이킹 관점에서 봤을때는 제네릭과 리터럴을 이용해서 구현하는 편이 더 좋다고 할 수 있겠습니다.

 


조건부 타입

마지막으로 배열과 객체를 모두 다룰 수 있는 조건부 타입으로 작성해 보겠습니다.

 

type ArrayLiteral<T extends ReadonlyArray<any>> = T[number];
type ObjectLiteral<T extends { [i: string]: any }> = T[keyof T];
type Literal<T> =
  T extends ReadonlyArray<any> ? ArrayLiteral<T> :
  T extends { [i: string]: any } ? OfObjectLiteral<T> :
  never;

 

댓글