이 글에는 타입스크립의 유틸리티 타입에 대한 개념적인 내용이 담겨있습니다.
타입스크립트를 사용하기 전에는 어떻게 개발을 했었나… 싶을 정도로 이제는 타입스크립트 없이는 개발하기 쉽지 않아졌다.
타입스크립트를 통한 안정성과, 코드 가독성은 개발에 있어 아주 마법같은 도구로 다가온다..
타입스크립트에서는 다양한 타입을 제공하는데 “기본 타입” 만을 사용하기에는 개발하기에 조금은 아쉬움이 있다
그래서 오늘은 자주쓰이고, 유용하게 사용되는 유틸리티 타입(Utility Types) 에 대해서 정리해보려고 한다.
“타입을 변경하는데 쉽고 용이하게 도와주는 특별한 타입”
유틸리티 타입을 굳이 사용하지 않고도, 기본 Interface, Generic 많을 사용하여 개발할 수 있겠지만, 유틸리티 타입을 사용하면 훨씬 간결한 문법으로 타입을 정의할 수 있다.
타입스크립트에서 제공하는 제네릭, 맵드 타입, 조건부 타입 등 을 이용해 실무에서 자주 사용되는 타입을 미리 만들어놓은 하나의 “함수”이다.
*그렇기에 유틸리티 타입은 특정한 독립적인 코드 문법이 아니다.*
그렇기 때문에 유틸리티 타입을 사용하며 내부적으로 어떤 문법으로 어떻게 구현되었는지 생각해보고 찾아보면 이해하는데 큰 도움이 될 것이다.

[출처] 인프런 - 한입 크기로 잘라먹는 타입스크립트
“모든 속성을 optional (
?)로 변경한 새로운 타입을 반환”
- 모든 속성 타입을 옵셔널으로 만드는건 좋지않기 때문에
PICK이나OMIT유틸리티 타입을 더 활용- 데이터에서 일부만 업데이트하고 싶을 때 사용하면 좋음
interface User {
name: string;
age: number;
phone: number;
}
type Partial_User = Partial<User> // 인터페이스 User의 속성을 모두 optional 설정
/*
type Partial_User = {
name?: string | undefined;
age?: number | undefined;
phone?: number | undefined;
}
*/
구현 방법
type Partial<T> = {
[P in keyof T]? : T[p];
}
“T 에 모든 속성을 필수로 변경한 새로운 타입을 반환”
- Partial 의 정반대 기능
- 모든 데이터를 필수적으로 입력을 받아야 할 때 사용하면 좋음
interface User {
name?: string;
age?: number;
phone: number;
}
// 인터페이스 User의 속성을 모두 일반 타입으로
type Required_User = Required<User>;
/*
type Required_User = {
name: string;
age: number;
phone: number;
}
*/
구현 방법
type Required<T> = {
[P in keyof T] - ? : T[P];
}
“T의 모든 속성을 읽기 전용 (readonly)로 변경한 새로운 타입을 반환”
- 한번 할당하고 나면 값을 변경할 수 없도록 막는다
- 객체의 불변성을 지키고 싶을 때, 객체의 원본을 지키고 싶을 때 사용
interface User {
name?: string;
age?: number;
phone: number;
}
type Readonly_User = Readonly<User>; // 인터페이스 User의 속성을 모두 readonly 설정
/*
type Required_User = {
readonly name?: string | undefined;
readonly age?: number | undefined;
readonly phone: number;
}
*/
const user: Readonly<User> = {
name: '홍길동',
age: 22,
phone: 111,
};
user.age = 11; // ERROR !!
구현 방법
type ReadOnly<T> = {
readonly [p in keyof T] : T[p];
}
“
T(Type)로 부터K(key)에 해당하는 속성을 선택하여 따로 모아 타입을 반환”
- 타입 T 에서 특정 속성 K만 모아서 새로운 타입을 만든다.
- 특정 정보만 필요한 함수나 컴포넌트에 데이터를 넘겨줄 때 사용
interface User {
name: string;
age: number;
email: string;
isValid: boolean;
}
type Key = 'name' | 'email';
type Pick_User = Pick<User, Key>; // User 인터페이스의 속성에서 'name', 'email' 만 선택
/*
type Pick_User = {
name: string;
email: string;
}
*/
const user: Pick<User, Key> = {
name: 'inpa',
email: 'inpa@naver.com',
};
구현 방법
type Pick<T, K extends keyof T> = {
[p in K] : T[p]
}
“
T(Type)로 부터K(key)에 해당하는 속성을 제외한 나머지를 따로 모아 타입을 반환”
Pick<T, K>의 반대 역할- 타입 T 에서 특정 속성 K만 빼고 나머지를 모아 새로운 타입을 만든다.
- 당장에 필요없는 불필요한 데이터(민감한 정보) 를 제외하고 싶을 때 사용
interface User {
name: string;
age: number;
email: string;
isValid: boolean;
}
type Key = 'name' | 'email';
type Omit_User = Omit<User, Key>; // User 인터페이스의 속성에서 'name', 'email' 제외
/*
type Omit_User = {
age: number;
isValid: boolean;
}
*/
const user: Omit<User, Key> = {
age: 44,
isValid: true,
};
구현 방법
// Pick 유틸리티 타입과 Exclude 유틸리티 타입을 응용해서 조합한 버전이라 할 수 있다
// 즉, 해당 Type을 Exclude한 나머지 타입들을 Pick
// extends keyof any 라는 뜻은 오로지 타입만 받게 하도록 설정
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
Omit<TYPE, KEY>
“
K(key)속성으로 제네릭의T(type)을 속성값의 타입으로 지정하는 새로운 객체 타입 반환”
- 객체의 키가 동적이거나, 여러개일 때 사용하면 좋다.
type Key = 'name' | 'age' | 'phone';
type Record_User = Record<Key, number>; // 유니온 Key의 'name', 'age', 'phone' 타입들을 속성으로 하여 number 타입으로 설정
/*
type Record_User = {
name: number;
age: number;
phone: number;
}
*/
const user: Record<Key, number> = {
name: 9999,
age: 22,
phone: 111,
};
구현 방법
type Record<K extends keyof any, T> = {
[P in K]: T;
};
Record<KEY, TYPE>
“유니온 타입
T에서U를 제외한 나머지 타입을 반환”
- 말 그대로 유니온 타입에서 특정한 타입만 제외하고 싶을 때 사용한다.
type Type = string | number | object; // 유니온 타입
// Type의 string | number | object 에서 number을 제외
type Exclude_Type = Exclude<Type, number>;
/*
type Exclude_Type = string | object
*/
const a: Exclude<Type, number> = 'Only string';
const b: Exclude<Type, number> = { name: '홍길동' };
const c: Exclude<Type, object> = 'Only string';
const d: Exclude<Type, object> = 123123;
구현 방법
// 미리 정의된 조건부 타입 (T가 U에 들어갈 수 있으면 never, 아니면 T
type Exclude<T, U> = T extends U ? never : T;
Exclude<TYPE1, TYPE2>
“
T와U에서 겹치는 부분을 추출하여 반환”
Exclude<T, U>와 반대로 유니온 타입 중 원하는 부분을 추출하여 사용하고 싶을 때 사용한다.
type Type1 = string | number | object | null;
type Type2 = number | boolean;
// 유니온 Type1에서 유니온 Type2 와 일치하는 타입 number만 추출
type Extract_Type = Extract<Type1, Type2>;
/*
type Extract_Type = number
*/
const a: Extract<Type1, Type2> = 123123;
구현 방법
// T가 U에 할당될 수 있으면 T, 아니면 never
type Extract<T, U> = T extends U ? T : never;
Extract<TYPE1, TYPE2>
“함수를 제네릭 타입으로 받아 Return 타입을 반환”
- 함수의 결과 타입을 재사용하고 싶을 때 사용
function fn(str: string): number {
return +str;
}
type Type = ReturnType<typeof fn>; // 함수의 리턴 타입을 반환
/*
type Type = number
*/
const a: ReturnType<typeof fn> = 1234;
const b: ReturnType<typeof fn> = 'Only string'; // TS2322: Type '123' is not assignable to type 'string'.
구현 방법
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
ReturnType<TYPE>
위에서 정리한 자주 사용되는 유틸리티 타입 이외에도 정말 많은 유틸리티 타입이 있다. 사실 모든 것을 다 알지 못하더라도 어떤 것들이 있는지만 알고 있다면 나중에 필요하다고 느낄 때 찾아보며 사용해보면 되지 않을까 싶다.|
마치 무궁무진한 기능을 제공하는 파이썬 처럼…
미처 정리하지 못했지만, 알고 있으면 좋을 것 같은 유틸리티 타입을 정리하며 마친다.
| 유틸리티 타입 | 설명 |
|---|---|
NonNullable<T> | TYPE에서 null과 undefined를 제외한 새로운 타입 반환 (유니언) |
Parameters<T> | TYPE의 매개변수 타입을 새로운 튜플 타입으로 반환 (함수, 튜플) |
ConstructorParameters<T> | TYPE의 매개변수 타입을 새로운 튜플 타입으로 반환 (클래스, 튜플) |
InstanceType<T> | TYPE의 인스턴스 타입을 반환 (클래스) |
ThisParameterType<T> | TYPE의 명시적 this 매개변수 타입을 새로운 타입으로 반환 (함수) |
OmitThisParameter<T> | TYPE의 명시적 this 매개변수를 제거한 새로운 타입을 반환 (함수) |
ThisType<T> | TYPE의 this 컨텍스트(Context)를 명시, 별도 반환 없음! (인터페이스) |
인프런 - 한입 크기로 잘라먹는 타입스크립트