타입스크립트 유틸리티 타입 직접 구현해보기

타입스크립트에서 기본적으로 제공하는 유틸리티 타입을 직접 구현해보면서 이해하자


타입스크립트에서 기본적으로 제공하는 유틸리티 타입들이 있다. (참고 - 타입스크립트 공식 문서)

많이 사용되는 유틸리티 타입 중 몇 가지를 직접 구현해보면서 유틸리티 타입을 다시 한 번 이해해보자!

아래의 Post 타입은 기준이 되는 타입이다.

interface Post {
  title: string;
  description: string;
  author: {
    id: number;
    name: string;
    age: number;
  };
  tags?: string[];
};

Partial<T>

Partial 타입은 객체 타입의 모든 프로퍼티를 선택적 프로퍼티로 바꿔준다. mapped type으로 Partial을 구현할 수 있다.

interface Post {
  title?: string;
  description?: string;
  author?: {
    id: number;
    name: string;
    age: number;
  };
  tags?: string[];
};
 
type Partial<T> = {
  [key in keyof T]?: T[key];
};

Required<T>

Required 타입은 Partial 타입과 반대로 객체 타입의 모든 프로퍼티를 필수 프로퍼티로 바꿔준다.

interface Post {
  title: string;
  description: string;
  author: {
    id: number;
    name: string;
    age: number;
  };
  tags: string[];
};
 
type Required<T> = {
  [key in keyof T]-?: T[key];
};

모든 프로퍼티가 필수 프로퍼티가 되려면 해당 타입의 프로퍼티들이 모두 존재해야 한다. 단순히 [key in keyof T]: T[key];를 사용하면 기존 타입 T와 같은 타입이 되어버린다.

따라서 프로퍼티를 선택적으로 바꿔주는 ? 키워드 앞에 -를 붙여 선택적 프로퍼티의 반대를 갖는다는 뜻으로 정의를 하면 모든 프로퍼티들이 필수 프로퍼티가 된다.

Readonly<T>

객체 타입의 모든 프로퍼티들을 읽기 전용 프로퍼티로 바꿔준다.

interface Post {
  readonly title: string;
  readonly description: string;
  readonly author: {
    id: number;
    name: string;
    age: number;
  };
  readonly tags?: string[];
};
 
type Readonly<T> = {
  readonly [key in keyof T]: T[key];
};

Pick<T, K>

객체 타입의 특정 프로퍼티만 필요로 할 때 사용한다.

이전 버전에서 사용하던 타입에서 새로운 필수 프로퍼티를 추가할 경우 이전 버전 타입을 사용하는 모든 변수는 에러가 발생한다. 이 경우 새로 확장한 타입에서 이전에 사용하던 프로퍼티만 사용하도록 새로운 타입을 만들어낼 수 있는데 이 때 사용할 수 있는 타입이 Pick이다.

interface Post {
  title: string;
  description: string;
};
 
type Pick<T, K extends keyof T> = {
  [key in K]: T[key];
};
 
let post:Pick<Post, "title" | "description"> = {
  title: '제목',
  description: '설명'
};

Omit<T, K>

Omit은 Pick과 반대로 객체 타입의 특정 프로퍼티를 제거할 때 사용한다.

interface Post {
  title: string;
  description: string;
};
 
type Exclude<T, U> = T extends U ? never : T;
 
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
 
let post:Omit<Post, "author" | "tags"> = {
  title: '제목',
  description: '설명'
};

Record<K, V>

객체 타입을 Map 구조로 만들때 사용하며, 동일한 패턴을 갖는 객체 타입을 정의할 수 있다.

type Record<K extends keyof any, V> = {
  [key in K]: V
};
 
type UrlInfo = {
  url: string;
  size: number;
};
 
type Thumbnail = Record<"lg" | "md" | "sm", UrlInfo>;
 
let thumbnail: Thumbnail = {
  lg: {
    url: '',
    size: 0,
  },
  md: {
    url: '',
    size: 0,
  },
  sm: {
    url: '',
    size: 0,
  },
};

Exclude<T, U>

T 타입에서 U 타입을 제거할 때 사용한다. (설명은 여기로)

type Exclude<T, U> = T extends U ? never : T;

Extract<T, U>

Exclude와 반대로 T 타입에서 U 타입을 추출할 때 사용한다. (설명은 여기로)

type Extract<T, U> = T extends U ? T : never;

ReturnType<T>

함수의 반환 값을 추출할 때 사용한다. 함수의 반환 값을 구하기 위해서는 제네릭 T 타입 자체가 함수가 되어야 하기 때문에 T에 제한을 걸어둔다.

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : never;
 
function getStr() {
  return '123';
};
 
function getNum() {
  return 123;
};
 
type ReturnStrType = ReturnType<typeof getStr>;
type ReturnNumType = ReturnType<typeof getNum>;
 
// typeof 변수는 아래와 같이 바꿔 표현할 수 있다.
// type ReturnStrType = ReturnType<() => string>;
// type ReturnNumType = ReturnType<() => number>;