본문 바로가기

Language/TypeScript

<TS> 유틸리티 타입으로 반복 줄이기

1. 반복 줄이기

 - 타입스크립트를 사용하다보면 실제 런타임에 동작하지 않는 코드(타입 관련 코드)임에도 반복되는 작업들을 하는 경우가 생긴다.

 

 - 실제 런타임에 동작하는 코드에서는 다양한 방식으로 반복을 줄이기 위해 노력한다. 하지만 우리는 타입스크립트의 타입과 관련된 부분에서는 크게 신경쓰지 않거나 어떻게하면 좋을지 모르곤 한다. 이를 해결하기 위해 타입스크립트에서 어떻게 이러한 부분을 해결할 수 있을지 알아보자.

 

* 확장

 - 인터페이스의 확장은 반복을 제거하는 방법 중 하나이다.

 

interface Person {
  firstName: string;
  lastName: string;
}

interface PersonWithBirthDate extends Person {
  birth: Date;
}

 

 - 타입을 확장하는 경우에는 인터섹션 연산자(&)를 쓸 수도 있다. 하지만 이 방식은 일반적이지는 않다.

 

type PersonWithBirthDate = Person & { birth: Date };

 

* Pick

 - 만약 확장이 아니라 부분만 표현하고 싶다면 어떻게 해야 반복적인 코드를 줄일 수 있을까.

 

interface State {
  userId: string;
  pageTitle: string;
  recentFiles: string[];
  pageContents: string;
}

interface TopNavState {
  userId: string;
  pageTitle: string;
  recentFiles: string[];
}

 

 - 위와 같이 문맥상 확장하기보다는 부분집합으로 사용해야하는 경우가 있을 수 있다. 이런 경우 인덱싱을 사용하여 타입 중복을 방지할 수 있다.

 

type TopNavState = {
  userId: State['userUd'];
  pageTitle: State['pageTitle'];
  recentFiles: State['recentFiles'];
};

 

 - 하지만 이 또한 반복이 존재한다. 타입시스템만 아니라면 반복문을 돌릴 수 있을 것 같은 느낌이 든다. 이는 다음과 같이 해결할 수 있다.

 

type TobNavState = {
  [k in 'userId' | 'pageTitle' | 'recentFiles']: State[k]
};

 

 - 매핑된 타입은 배열의 필드를 루프 도는 것과 동일하게 동일하다. 이제 이를 추상화하여 여러 곳에서 재사용하고 싶을 것이다. 이 때에는 제너릭 타입을 활용할 수 있다.

 

type Pick<T, K> = { [k in K]: T[k] };

type TopNavState = Pick<State, 'userId' | 'pageTitle' | 'recentFiles'>;

 

 - 위와 같은 방식으로 작성하면 좋을 것 같다. 하지만 실제로 위를 사용하면 에러가 출력된다. 실제 표준 라이브러리에 포함된 Pick은 다음과 같이 작성되어 있다.

 

type Pick<T, K extends keyof T> = {
  [k in K]: T[k]
};

interface Name {
  first: string;
  last: string;
}

type FirstLast = Pick<Name, 'first' | 'last'>;
type FirstMiddle = Pick<Name, 'first' |'middle'>; // 오류

 

 - 위와 같이 작성되어야 하는 이유는 K가 실제 T가 갖고 있는 키의 부분집합이어야 하기 때문이다. 즉 keyof T의 부분집합이어야 한다. extends 를 통해 부분집합을 표현하여 제너릭 타입을 제한할 수 있다.

 

* Partial

 - 코드를 작성하면서 객체 속성 내 대부분을 선택 필드로 둬야할 경우들이 있다.

 

interface Options {
  width: number;
  height: number;
  color: string;
  label: string;
}

interface OptionsUpdate {
  width?: number;
  height?: number;
  color?: string;
  label?: string;
}

class UIWidget {
  constructor(init: Options) { //... }
  update(options: OptionsUpdate) { //...}
}

 

 - 위와 같은 반복은 매핑된 타입과 keyof를 통해 해결할 수 있다.

 

type OptionsUpdate = {[k in keyof Options]?: Options[k]};

 

 - 위 패턴은 표준라이브러리에 Partial로 포함되어 있다.

 

class UIWidget {
  constructor(init: Options) { //... }
  update(options: Partial<Options>) { //... }
}

 

* ReturnType

 - 이미 존재하는 값의 형태를 같게 타입을 정의하고 싶은 경우가 있다.

 

const InitOptions = {
  width: 500,
  height: 400,
  color: '#00ff00',
  label: 'VGA',
};

// Before
interface Options {
  width: number;
  height: number;
  color: string;
  label: string;
}

// After
type Options = typeof InitOptions;

 

 - 이는 알다시피 typeof를 사용하여 간단하게 해결할 수 있다. 그렇다면 함수나 메서드의 반환값의 형태를 타입으로 정의하고 싶다면 어떻게 해야 할까.

 

function getUserInfo(userId: string) {
  // ...
  return {
    userId,
    name,
    age,
    birth,
  }
}

 

 - 위와 같은 함수는 반환타입이 타입스크립트에 의해 추론가능하다. ({ userId: string; name: string; age: number; birth: Date })

 

 - 이 경우 표준 라이브러리에 ReturnType 제너릭을 사용할 수 있다.

 

type UserInfo = ReturnType<typeof getUserInfo>;

 

2. 유틸리티 타입

 - 지금까지 반복을 줄이기 위한 방법으로 표준 라이브러리에 있는 제너릭을 활용하였다. 이를 유틸리티 타입이라 하며, 타입스크립트는 위에서 봤던 것과 같이 글로벌로 선언된 몇 가지 유틸리티 타입을 제공하고 있다.

 

 - 위에서 언급한 Pick Partial ReturnType 외에도 아래와 같이 많은 타입을 제공하고 있다.

www.typescriptlang.org

 - 이 중에서 자주 사용할 법한 Required Readonly InstanceType 정도까지만 다뤄보겠다.

 

* Required

 - 모든 프로퍼티가 필수로 설정된 타입을 구현하고 싶다고 가정하자.

 

 

 - 이는 위에서 봤었던 Partial과 정반대로 동작하고 있다. 실제 Required코드는 다음과 같이 이루어져 있다.

 

* Readonly

 - Readonly는 이름에서도 알 수 있듯이 모든 속성을 읽기 전용으로 하는 타입을 만든다.

 

 

 - 위 코드에서 harry라는 유저에 MyProps에 맞게 name, birth, email 프로퍼티를 넣었고, Readonly 유틸리티 타입을 사용했다.

 

 - name과 옵셔널한 프로퍼티인 email 모두 수정이 불가능함을 알 수 있다. 구현방식은 아래와 같다.

 

 

* InstanceType

 - InstanceType은 클래스의 인스턴스 타입으로 구성된 타입이며, 다음과 같이 사용할 수 있다.

 

 

 


참고

 

 

이펙티브 타입스크립트 - YES24

타입스크립트는 타입 정보를 지닌 자바스크립트의 상위 집합으로, 자바스크립트의 골치 아픈 문제점들을 해결해 준다. 이 책은 《이펙티브 C++》와 《이펙티브 자바》의 형식을 차용해 타입스

www.yes24.com

 

 

Documentation - Utility Types

Types which are globally included in TypeScript

www.typescriptlang.org

 

'Language > TypeScript' 카테고리의 다른 글

<TS> 태그드 유니온 패턴  (0) 2022.11.11
<TS> 타입가드  (0) 2022.09.03
<TS> 효과적으로 타입 명시하기  (0) 2022.07.16