타입스크립트 유틸리티 타입 직접 구현해보기
타입스크립트에서 기본적으로 제공하는 유틸리티 타입을 직접 구현해보면서 이해하자
타입스크립트에서 기본적으로 제공하는 유틸리티 타입들이 있다. (참고 - 타입스크립트 공식 문서)
많이 사용되는 유틸리티 타입 중 몇 가지를 직접 구현해보면서 유틸리티 타입을 다시 한 번 이해해보자!
아래의 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>;