저번 면접때 유틸리티타입에 관한 질문을 받았었다.
타입스크립트를 이제 막 접했던 나에게는 생소한 타입이었는데
면접관님께서 Utility Type이 실제로 어떤 동작원리를 갖는지 코드 편집기의 레퍼런스를 보며 공부하면 좋을 것 같다는 피드백을 주셔서 이번 기회를 통해 제대로 파헤쳐보았다!
먼저, 유틸리티 타입에 대해 알아보기 전에 레퍼런스 코드를 보다보면
in
, keyof
, in keyof
문법을 자주 보게되는데,,,,
동작을 이해하려면 이것부터 정리하고 갈 필요가 있었다.
in
자바스크립트의 for in
과 같이 순회하여 실행하는 문법이다.
type Heroes = 'Hulk' | 'Thor' | 'Capt';
type HeroProfiles = { [K in Heroes]: number };
const heroInfo: HeroProfiles = {
Hulk: 54,
Thor: 1000,
Capt: 33,
}
Heroes 타입의 세가지 문자열을 순회하여 key로 만들고 타입을 number로 지정하는 heroInfo의 타입을 정의하였다.
{ Hulk: number } // 첫번째 순회
{ Thor: number } // 두번째 순회
{ Capt: number } // 세번째 순회
type HeroProfiles = {
Hulk: number;
Thor: number;
Capt: number;
}
keyof
타입 T 의 모든 key 값을 Union Type으로 가져오는 문법
interface User {
id: number;
name: string;
age: number;
gender: "m" | "f";
}
type UserKey = keyof User; // 'id' | 'name' | 'age' | 'gender'
const uk: UserKey = "id"; // User의 키 값 중 한개를 사용하면 오류가 안남
in keyof
keyof T에 속하는 모든 프로퍼티를 순회하는 문법
type Partial<T> = { [P in keyof T]?: T[P]; }
해당 프로퍼티의 Value를 P로 가져와서 새로운타입을 만든다.
이렇게 레퍼런스에서 주로 보이는 기본 문법을 알아보았다면, 본격적으로 유틸리티 타입에 대해 하나씩 알아보자!!
: T의 모든 프로퍼티를 옵셔널 프로퍼티로 변경한 새로운 타입을 반환
type Partial<T> = { [P in keyof T]?: T[P]; };
예시
interface User {
id: number;
name: string;
age: number;
gender: "m" | "f";
}
let admin: Partial<User> = {
id: 1,
name: "Bob",
};
User 타입의 모든 프로퍼티가 필수로 들어가지 않아도 오류가 나지 않는다!
즉, 타입의 모든 프로퍼티를 옵셔널 (?) 로 바꿔주는 것이다.
interface User {
id?: number;
name?: string;
age?: number;
gender?: "m" | "f";
}
코드로 작성하면 이렇다.
: T의 모든 프로퍼티를 필수 프로퍼티로 변경한 새로운 타입을 반환
type Required<T> = { [P in keyof T]-?: T[P]; };
예시
interface User {
id: number;
name: string;
age?: number; // 옵셔널이지만
}
let admin2: Required<User> = {
id: 1,
name: "Bob",
age: 30, // 필수로 바뀌어버림!
};
옵셔널을 제거해버린다!
: T의 모든 프로퍼티를 읽기 전용으로 변경한 새로운 타입을 반환
type Readonly<T> = { readonly [P in keyof T]: T[P]; };
예시
interface User {
id: number;
name: string;
age?: number;
}
let admin3: Readonly<User> = {
id: 1,
name: "Bob",
};
admin3.id = 4; // error : 읽기전용 속성으로 변경되어 재할당 불가능!
: 오브젝트의 각 프로퍼티마다 키는 K 타입 중 하나이고, 값은 T 타입인 새로운 타입을 반환
type Record<K extends keyof any, T> = { [P in K]: T; };
예시1
interface MovieInfo {
rank: number;
director: string;
}
type MovieTitle = "Spiderman" | "Dune2" | "Avatar2";
const cats: Record<MovieTitle, MovieInfo> = {
Spiderman: { rank: 10, director: "Jon Watts" },
Dune2: { rank: 5, director: "Denis Villeneuve" },
Avatar2: { rank: 16, director: "James Cameron" },
};
예시2
interface User {
id: number;
name: string;
age: number;
}
function isVaild(user: User) {
const result: Record<keyof User, boolean> = {
id: user.id > 0,
name: user.name !== "",
age: user.age > 0,
};
return result;
}
: 타입 T의 지정한 Key로만 구성된 프로퍼티를 갖는 새로운 타입을 반환
type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
예시
interface User {
id: number;
name: string;
age: number;
}
const admin5: Pick<User, "id" | "name"> = {
id: 0,
name: "sam",
};
: 타입 T의 지정한 Key를 제외한 프로퍼티를 갖는 새로운 타입을 반환
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
예시
interface User {
id: number;
name: string;
age: number;
gender: "m" | "f";
}
const admin6: Omit<User, "age" | "gender"> = {
id: 0,
name: "sam",
};
: 타입 T와 타입 U의 차집합의 타입을 갖는 새로운 타입을 반환
type Exclude<T, U> = T extends U ? never : T;
✨ Omit과 비슷해보이지만, Omit은 프로퍼티의 차집합, Exclude 는 타입의 차집합이다.
예시
type T1 = string | number | boolean;
type T2 = Exclude<T1, number | string>; // boolean
: null 과 undefined를 제외한 타입을 갖는 새로운 타입을 반환
type NonNullable<T> = T extends null | undefined ? never : T;
예시
type T3 = string | null | undefined | void;
type T4 = NonNullable<T3>; // string | void
[참조]
https://typescript-kr.github.io/pages/utility-types.html, https://seokzin.tistory.com/entry/TypeScript-유틸리티-타입-구현하기-Utility-Types, https://driip.me/75ce196d-484d-48b4-9108-b7d4f81879a6