타입스크립트가 자바스크립트에 타입을 더해서 코드를 더 안정적이고 읽기 좋게 만들어준다는 건 다들 아시죠? 😉 타입스크립트에는 이미 만들어진 타입을 더 편하게 사용하도록 도와주는 유틸리티 타입(Utility Types)이라는 정말 유용한 기능들이 내장되어 있어요. 마치 기존 타입을 요리조리 바꾸거나 조합할 수 있는 마법 도구 같달까요? ✨
오늘은 타입스크립트 개발자라면 꼭 알아둬야 할, 자주 쓰이는 유틸리티 타입 10가지를 쉽고 재미있게 알아볼게요! 이것만 알아도 타입스크립트 코드가 훨씬 깔끔해질 거예요.
Partial<T>
Partial<T>
은 타입 T
안에 있는 모든 속성(property)들을 '있어도 되고~ 없어도 되는~' 선택적인 속성으로 바꿔주는 마법을 부려요. 물음표(?
)를 모든 속성 뒤에 붙여준다고 생각하면 쉬워요!
언제 쓸까요? 예를 들어 사용자 정보 중에 일부만 업데이트하고 싶을 때 딱이죠!
interface UserInfo {
name: string;
age: number;
email: string;
}
// Partial을 사용하면 UserInfo의 모든 속성이 옵셔널이 돼요.
function updateUser(user: Partial<UserInfo>) {
// 여기에 사용자 정보 업데이트 로직이 들어 가겠죠?
console.log("업데이트 정보", user);
}
// 사용 예시
updateUser({ name: "Aiden" }); // 이름만 바꿔도 OK!
updateUser({ age: 30, email: "aiden@example.com" }); // 나이랑 이메일만 바꿔도 OK!
// updateUser({ nickname: "홍길동" }); // 에러! UserInfo에 없는 속성은 안돼요.
Required<T>
Required<T>
는 Partial<T>
와 정반대예요. 타입 T
안에 있는 모든 속성들을 '무조건 있어야 해!' 필수적인 속성으로 만들어줘요. 혹시 옵셔널(?
)이었던 속성이 있다면 그 물음표를 떼버리는 거죠!
언제 쓸까요? 모든 정보가 반드시 다 입력되어야 하는 상황에서 유용해요.
interface UserProfile {
name: string;
age?: number; // 나이는 선택 사항이라고 해봐요.
email: string;
}
// Required를 쓰면 age도 필수가 돼요!
const completeUser: Required<UserProfile> = {
name: "Aiden",
age: 30, // age가 없으면 에러 발생!
email: "aiden@example.com"
};
Readonly<T>
Readonly<T>
는 타입 T
의 모든 속성을 읽기 전용(readonly)으로 만들어요. 한번 값을 할당하고 나면 절대 바꿀 수 없게 잠가버리는 거죠! 🔒
언제 쓸까요? 객체의 불변성(immutability)을 지키고 싶을 때, 즉 실수로라도 원본 객체가 변경되는 것을 막고 싶을 때 사용해요.
interface Point {
x: number;
y: number;
}
const startPoint: Readonly<Point> = { x: 0, y: 0 };
// startPoint.x = 100; // 에러! 읽기 전용 속성이므로 'x'에 할당할 수 없다고 나와요.
Pick<T, K>
Pick<T, K>
는 타입 T
에서 내가 원하는 속성 K
만 쏙쏙 골라서 새로운 타입을 만들어요. 마치 장바구니에서 원하는 물건만 고르듯이요! 🛒
언제 쓸까요? 특정 정보만 필요한 함수나 컴포넌트에 데이터를 넘겨줄 때 유용해요.
interface Product {
id: string;
name: string;
price: number;
description: string;
}
// Product 타입에서 'name'과 'price' 속성만 골라내서 새로운 타입을 만들어요.
type ProductNameAndPrice = Pick<Product, 'name' | 'price'>;
const productPreview: ProductNameAndPrice = {
name: "노트북",
price: 1500000
// id나 description 속성은 여기에 포함되지 않아요!
};
Omit<T, K>
Omit<T, K>
는 Pick<T, K>
의 반대예요. 타입 T
에서 특정 속성 K
만 쏙 빼고 나머지 속성들로 새로운 타입을 만들어요.
언제 쓸까요? 민감한 정보나 불필요한 정보를 제외하고 타입을 사용하고 싶을 때 좋아요.
interface User {
id: number;
name: string;
password?: string; // 비밀번호는 민감 정보!
email: string;
}
// User 타입에서 'password' 속성만 제외하고 새로운 타입을 만들어요.
type PublicUserInfo = Omit<User, 'password'>;
const publicUser: PublicUserInfo = {
id: 1,
name: "Aiden",
email: "aiden@example.com"
// password 속성은 여기에 없어요!
};
Record<K, T>
Record<K, T>
는 조금 특별한데요, 키(Key)의 타입이 K
이고 값(Value)의 타입이 T
인 객체 타입을 쉽게 만들 수 있게 해줘요. 딕셔너리나 해시맵 같은 구조의 타입을 정의할 때 아주 편리해요!
언제 쓸까요? 객체의 키가 동적이거나 여러 개일 때, 값의 타입은 동일하게 지정하고 싶을 때 유용해요.
// 페이지 종류를 키로 갖고, 해당 페이지의 제목(string)을 값으로 갖는 타입을 만들어볼까요?
type PageType = 'home' | 'about' | 'contact';
type PageTitles = Record<PageType, string>;
const pageTitles: PageTitles = {
home: "메인 페이지",
about: "회사 소개",
contact: "연락처"
// blog: "블로그" // 에러! PageType에 'blog'가 없으므로 키로 사용할 수 없어요.
};
// 사용자 ID(string)를 키로 갖고, 사용자 정보(UserInfo)를 값으로 갖는 타입
type UserMap = Record<string, UserInfo>;
const users: UserMap = {
"user-1": { name: "Alice", age: 25, email: "alice@example.com" },
"user-2": { name: "Bob", age: 30, email: "bob@example.com" }
};
Exclude<T, U>
Exclude<T, U>
는 타입 T
(주로 유니언 타입) 중에서 타입 U
에 해당하는 타입들을 제외하고 남은 타입들로 새로운 유니언 타입을 만들어요. 말 그대로 '제외'하는 거죠!
언제 쓸까요? 여러 개가 합쳐진 유니언 타입에서 특정 케이스만 제거하고 싶을 때 사용해요.
type Status = "loading" | "success" | "error" | "idle";
// Status 타입에서 "loading" 상태만 제외하고 싶어요.
type NonLoadingStatus = Exclude<Status, "loading">;
// 결과 타입 NonLoadingStatus는 "success" | "error" | "idle" 가 돼요!
let currentStatus: NonLoadingStatus = "success";
// currentStatus = "loading"; // 에러! NonLoadingStatus 타입에는 "loading"이 없어요.
Extract<T, U>
Extract<T, U>
는 Exclude<T, U>
와 반대로 동작해요. 타입 T
(주로 유니언 타입) 중에서 타입 U
에 해당하는 타입들만 쏙 골라내서 새로운 유니언 타입을 만들어요. '추출'하는 거죠!
언제 쓸까요? 유니언 타입 중에서 특정 타입들만 따로 뽑아내고 싶을 때 유용해요.
type EventType = "click" | "mouseover" | "keydown" | "keyup";
// EventType 중에서 마우스 관련 이벤트("click", "mouseover")만 뽑아내고 싶어요.
type MouseEventType = Extract<EventType, "click" | "mouseover">;
// 결과 타입 MouseEventType는 "click" | "mouseover" 가 돼요!
let mouseEvent: MouseEventType = "click";
// mouseEvent = "keydown"; // 에러! MouseEventType 타입에는 "keydown"이 없어요.
NonNullable<T>
NonNullable<T>
는 아주 직관적이에요! 타입 T
에서 null
과 undefined
를 싸악 제거한 타입을 만들어줘요. 값이 무조건 있다는 것을 보장하고 싶을 때 사용하죠.
언제 쓸까요? 어떤 값이 null
이나 undefined
일 가능성이 있지만, 특정 로직에서는 반드시 값이 있다고 가정하고 처리하고 싶을 때 유용해요.
type MaybeString = string | null | undefined;
let username: MaybeString = "Aiden";
// let username: MaybeString = null; // 이것도 가능하죠.
// NonNullable을 사용하면 null과 undefined가 제거된 string 타입만 남아요.
function printUserName(name: NonNullable<MaybeString>) {
// 이 함수 안에서는 name이 무조건 string이라고 확신할 수 있어요!
console.log(name.toUpperCase());
}
if (username) { // 실제 사용할 때는 이렇게 null/undefined 체크를 하고 넘겨주는 게 안전해요.
printUserName(username);
}
ReturnType<T>
ReturnType<T>
는 함수 타입 T
가 반환하는 값의 타입을 뽑아내서 새로운 타입을 만들어줘요. 함수의 결과 타입을 재사용하고 싶을 때 편리해요.
언제 쓸까요? 특정 함수의 반환 타입을 변수나 다른 함수의 매개변수 타입 등으로 명시하고 싶을 때 사용해요.
// 사용자 정보를 반환하는 함수 타입 정의
type GetUserFunction = (id: number) => { name: string; age: number };
// GetUserFunction 타입의 반환 타입을 뽑아내요.
type UserReturnType = ReturnType<GetUserFunction>;
// 결과 타입 UserReturnType은 { name: string; age: number } 가 돼요!
function processUser(user: UserReturnType) {
console.log(`사용자 이름 ${user.name}, 나이 ${user.age}`);
}
const getUser: GetUserFunction = (id) => ({ name: `User${id}`, age: 20 + id });
const userResult = getUser(5);
processUser(userResult); // processUser 함수는 UserReturnType 타입을 인자로 받아요.
와! 정말 다양한 유틸리티 타입들이 있죠? 타입스크립트가 제공하는 이런 마법 도구들을 잘 활용하면 훨씬 더 깔끔하고, 안전하고, 재사용성 높은 코드를 작성할 수 있답니다. 오늘 배운 10가지 유틸리티 타입들, 앞으로 코드 짤 때 꼭 한번 활용해보세요! 여러분의 타입스크립트 레벨이 한 단계 더 올라갈 거예요! 👍