사내 EAI, ESB 팀 프로젝트를 구현하는 중, 중요한 기능 구현을 맡았습니다.

페이지 하나 단위가 개발되어야 하기 때문에, 기존 타입이 모두 any로 추론되는 다른 기능과는 다른 컴포넌트 구조로 개발을 진행한 후, 점진적으로 구조를 개선하는 방향으로 제안을 해 볼 생각입니다 🥹

그 과정에서 타입스크립트 빌트인 유틸리티 타입만으로 정의하기 힘든 타입들이 몇 가지 있었습니다.

예시 타입을 들어보겠습니다.

interface ContentResponseProps extends ResponseModel {
  data: {
	  title: string;
	  content: {
		  id: string;
		  name: string;
		  address: string;
		  lastAddress: string;
	 }[];
  }
}

위 코드에서 data 필드의 타입을 추출하려면 어떻게 해야할까요?

중복된 타입을 선언하여 새로운 타입변수에 할당하거나,

type ContentDataType = ContentResponseProps['data'];

위의 케이스 처럼 뽑아서 사용할 수 있습니다.

그러나 이런 케이스로 사용하게 되면 재사용성이 낮다는 문제점이 존재합니다. 다른 타입에서 같은 패턴을 사용하려면 코드를 반복해야 합니다.

또 data 필드 안의 content 필드는 Array 타입입니다. 안의 컨텐츠의 데이터만 뽑고 싶습니다. 어떻게 해야할까요?

type ContentItem = ContentResponseProps['data']['content'][number];

위의 케이스 처럼 number 타입 인덱싱을 통해 추출해낼 수 있습니다. 그러나 직관적이지 못하고 재사용성이 낮다는 단점이 존재한다고 생각했습니다.

커스텀 유틸리티 타입 적용

때문에 유틸리티 타입을 커스텀하여 만들기로 했습니다.

type PropType<T, K extends keyof T> = T[K]; //1 번 케이스
 
type ArrayType<P extends Array> = P extends Array<infer T> ? T : unknown; //2번 케이스

PropType은 객체 타입 T에서 특정 키 K에 해당하는 속성의 타입을 추출하는 유틸리티 타입입니다.

  • T: 전체 객체 타입을 나타내는 첫 번째 매개변수
  • K extends keyof T: T의 키 중 하나여야 한다는 제약조건을 가진 두 번째 매개변수
  • T[K]: T 타입에서 K 키에 해당하는 속성의 타입을 반환

ArrayType은 배열 타입 P에서 요소의 타입을 추출하는 유틸리티 타입입니다.

  • P extends any[]: 배열 타입만 허용하는 제약조건을 가진 매개변수
  • P extends Array ? T : unknown:
    • infer T는 배열 요소의 타입을 추론하라는 의미
    • 조건을 만족하면 추론된 요소 타입 T를 반환
    • 조건을 만족하지 않으면 unknown 타입을 반환

똑같이 ContentDataType과 ContentItem 타입을 재정의 해보겠습니다.

 type ContentDataType = PropType<ContentResponseProps, 'data'>;
 type ContentItem = ArrayType<PropType<PropType<ContentResponseProps, 'data'>, 'content'>>;

확실히 코드베이스가 직관적으로 변하고 다른 곳에서도 재사용할 수 있기 때문에 코드 품질을 기대할 수 있게 된 것 같습니다.