본 포스팅은 Inflearn(인프런) 이정환님의 한 입 크기로 잘라먹는 Typescript 강의를 참고하여 작성되었습니다.
[Utility Type]
Utility Type
Utility Type이란 Generic, Mapped Type, 조건부 Type 등의 타입 조작 기능을 이용해 실무에서 자주 사용되는 타입을 미리 만들어 놓은 것을 의미한다. 기본적으로 TS에서 자체적으로 지원해주며 가장 많이 사용되는 Utility Type에 대해서 알아보자.
[Mapped Type 기반 Partial, Required, Readonly]
Partial<T>
- 특정 객체 타입의 모든 속성을 Optional Property로 전환해주는 Type이다. 정의되어 있는 타입이 존재하지만 사용자의 활동에 따라서 사용이 될 수도 있고 사용되지 않을 수도 있는 속성들에 대해 정의하기 좋은 타입이다. 예시로는 블로그 작성 시 제목, 글 내용, 태그 등 다양한 속성이 있지만 사용자가 모든 속성을 사용한다는 보장이 없기 때문에 블로그를 떠올리면 이해가 쉽다. 코드 예시를 살펴보자.
interface Post {
title: string,
tags: string[],
content: string,
thumbnailURL?: string
}
const draft: Partial<Post> = {
title: "제목",
content: "내용"
}
type Partial<T> = {
[key in keyof T]?: T[key]
}
Interface로 정의된 Post에는 4가지의 속성이 존재하지만 draft로 선언된 변수에는 2개의 속성만 할당이 되어있다. 그럼에도 오류가 발생하지 않는 이유는 변수 draft의 타입을 Partial<Post>로 정의하면서 모든 속성을 Optional Property로 바꾸었기 때문에 특정 속성이 존재하지 않아도 오류가 발생하지 않는 것이다.
마지막에 Type Alias로 Partial 타입을 자체적으로 생성해보았다. 적용되는 원리와 순서를 살펴보자.
1. Generic을 이용하여 T에 들어오는 객체의 속성들을 keyof 연산자를 통해서 Union Type으로 추출한다.
2. 다음으로 Mapped Type에서 제공되는 in 연산자를 통해 key를 Union Type으로 추출된 속성들에 하나씩 Mapping이 된다.
3. 그 속성들이 Indexed Access Type인 T[key]를 통해 속성을 추출되어 모든 것이 Optional Property로 전환이 된다.
Required<T>
- 특정 객체 타입의 모든 속성을 필수 속성으로 바꾸어주는 타입이다. Partial Type은 모두 선택적인 속성으로 바꾸어주었다면 Required Type은 모든 속성을 필수로 바꾸어주는 타입이다. 따라서 객체 타입을 초기에 정의할 때 특정 속성을 Optional Property로 정의를 해두었어도 변수를 선언하고 타입을 지정해줄 때 Required Type으로 지정을 해준다면 Optional Property를 포함한 모든 속성이 필수 속성이 되는 것이다. 코드 예시를 함께 살펴보자.
interface Post {
title: string,
tags: string[],
content: string,
thumbnailURL?: string
}
const A: Required<Post> = {
title: "title",
tags: ["tags"],
content: "content",
} //Error: thumbnailURL Property is neccessary
const B: Required<Post> = {
title: "title",
tags: ["tags"],
content: "content",
thumbnailURL: "url"
}
type Required<T> = {
[key in keyof T]-? : T[key]
}
thumbnailURL 속성은 Interface에서 Optional로 정의가 되었음에도 Required Type으로 인해 필수 속성으로 적용되는 것을 볼 수 있다.
하단에는 자체적으로 Required Type을 생성하는 코드인데 코드의 문법은 앞서 살펴본 Partial Type에서와 동일하지만 선택적 속성이 아닌 필수 속성으로 만들어주기 위해서는 "?"가 아닌 "-?"를 붙여주어야 한다.
Readonly<T>
- 특정 객체 타입의 모든 속성을 읽기 전용 속성으로 바꾸어주는 타입이다. 앞서 두가지 타입을 살펴보았으니 어떤 원리로 적용되고 생성되는지는 이해가 쉬울 것 같으므로 코드 예시만 살펴보자.
interface Post {
title: string,
tags: string[],
content: string,
thumbnailURL?: string
}
const A: Readonly<Post> = {
title: "title",
tags: ["tags"],
content: "content",
} //Error: thumbnailURL Property is neccessary in Readonly Type
const B: Readonly<Post> = {
title: "title",
tags: ["tags"],
content: "content",
thumbnailURL: "url"
}
type Readonly<T> = {
readonly [key in keyof T]: T[key]
}
[Mapped Type 기반 Pick, Omit, Record]
Pick<T,K>
- 객체 타입으로부터 특정 속성만 골라내는 타입이다. Generic으로 두가지를 정의하는데, T는 속성을 골라낼 객체 타입을 의미하고 K는 객체에서 골라낼 속성들을 의미한다. 아래 코드 예시를 보자.
interface Post {
title: string,
tags: string[],
content: string,
url?: string
}
const PostA: Pick<Post, "title" | "content"> = {
title: "title",
content: "content"
}
type Pick<T,K extends keyof T> = {
//K extends 'title'|'tags'|'content'|'url'
//'title'|'content' extends 'title'|'tags'|'content'|'url'
[key in K]: T[key];
}
Interface로 정의된 Post 타입은 4가지 속성을 갖고 있지만 선언한 변수 PostA는 2가지 속성만 사용을 하고자 한다. 이 때 Pick Type을 사용해서 사용할 속성만 골라낼 수 있는 것이다.
하단에 작성된 Pick Type을 생성하는 방법을 보면 앞서 살펴보았던 keyof 연산자를 사용하지 않고 extends 키워드와 keyof 연산자를 함께 사용하여 K가 T의 속성들의 Serve Type이라는 것을 명시해주며 동작하게 된다.
Omit<T,K>
- 객체 타입으로부터 특정 속성을 제거하는 타입이다. Omit Type은 Pick Type과 반대되는 타입으로 Pick Type의 단점을 보완할 수 있는 타입이다. 예를 들어 특정 객체의 속성이 많은 경우에 Pick Type으로 필요한 속성을 모두 빼내어 사용하는 것보다 필요하지 않은 속성을 제거해두고 사용하는 것이 편리한 경우가 있을 것이다. 그러한 경우에 사용하는 타입이다.
interface Post {
title: string,
tags: string[],
content: string,
url?: string
}
const PostB: Omit<Post,"title"> = {
tags: ["ts"],
content: "content",
url: "url"
}
type Omit<T,K extends keyof T> = Pick<T, Exclude<keyof T,K>>
//1.Omit<Post,"title"> = Pick<Post, Exclude<keyof Post, "title">
//2.Omit<Post,"title"> = Pick<Post, Exclude<'title','tags',content','url', "title">
//3.Omit<Post,"title"> = Pick<Post, 'tags',content','url'>
//4.Omit<Post,"title"> = Pick<Post, 'tags',content','url'>
사용 예시는 위와 같으며 직접 Omit Type을 작성하는 과정도 하단에 적혀있는데 동작의 순서를 살펴보면 Pick Type과 Omit Type이 반대되는 것을 확인해볼 수 있다.
Record<K,V>
- 동일한 속성 패턴을 갖는 객체 타입을 정의하는데 사용하는 타입이다. 내부 속성은 모두 같지만 외부 속성들의 이름이 다른 다중 객체에서 사용하기 용이하다. 코드 예시를 한 번 보자.
type A = {
small: {
url: string
},
medium: {
url: string
},
large: {
url: string
},
xlarge: {
url: string
},
}
type B = Record<
"small" | "medium" | "large" | "xlarge",
{ url: string; size: number }
>;
type Record<K extends keyof any, V> = {
[key in K]: V;
}
K로는 적용할 외부 속성들을 Union Type으로 입력하고 V에는 삽입할 내부 속성을 입력하면 된다.
하단에 자체적으로 생성하는 Record Type 또한 확인해 볼 수 있다.
[Exclude, Extract, Return Type]
Exclude<T,U>
- T에서 U를 제거하는 타입을 의미한다. 코드 예시로 살펴보며 과정을 이해해보자.
type A = Exclude<string | boolean, boolean>;
/**
* 1단계
* Exclude<string, boolean>
* Exclude<boolean, boolean>
* 2단계
* string | never
* 3단계
* string
*/
type Exclude<T,U> = T extends U ? never : T;
Extract<T,U>
- T에서 U를 추출하는 타입을 의미한다. 코드 예시로 살펴보며 과정을 이해해보자.
type B = Extract<string | boolean, boolean>
type Extract<T,U> = T extends U ? T : never;
ReturnType<T>
- 함수의 반환값의 타입을 추출하는 타입을 의미한다. 코드 예시로 살펴보며 과정을 이해해보자.
function funcA(){
return "string"
}
function funcB(){
return 10
}
type ReturnA = ReturnType<typeof funcA>
type ReturnB = ReturnType<typeof funcB>
type ReturnType<T extends (...args: any) => any> = T extends (...args:any) => infer R ? R : never;
'Typescript' 카테고리의 다른 글
[TypeScript] - (9) 조건부 타입 (1) | 2024.08.22 |
---|---|
[TypeScript] - (8) Type 조작 (0) | 2024.08.14 |
[TypeScript] - (7) Generic(제네릭) (2) | 2024.08.07 |
[TypeScript] - (6) Class (0) | 2024.08.05 |
[Typescript] - (5) Interface (0) | 2024.08.01 |