타입스크립트 고인물처럼 쓰는 법? 유틸리티 타입 10개 정복!

홍태극·2023년 8월 29일
0

타입스크립트 고인물처럼 쓰는 법? 유틸리티 타입 10개 정복!

타입스크립트가 자바스크립트에 타입을 더해서 코드를 더 안정적이고 읽기 좋게 만들어준다는 건 다들 아시죠? 😉 타입스크립트에는 이미 만들어진 타입을 더 편하게 사용하도록 도와주는 유틸리티 타입(Utility Types)이라는 정말 유용한 기능들이 내장되어 있어요. 마치 기존 타입을 요리조리 바꾸거나 조합할 수 있는 마법 도구 같달까요? ✨

오늘은 타입스크립트 개발자라면 꼭 알아둬야 할, 자주 쓰이는 유틸리티 타입 10가지를 쉽고 재미있게 알아볼게요! 이것만 알아도 타입스크립트 코드가 훨씬 깔끔해질 거예요.

1. 부분만 골라 쓰기! 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에 없는 속성은 안돼요.

2. 전부 다 필수! 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"
};

3. 수정은 안 돼요! 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'에 할당할 수 없다고 나와요.

4. 필요한 것만 쏙! 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 속성은 여기에 포함되지 않아요!
};

5. 필요 없는 건 싹! 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 속성은 여기에 없어요!
};

6. 키-값 짝꿍 만들기! 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" }
};

7. 너는 빼고! 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"이 없어요.

8. 너만 골라낼게! 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"이 없어요.

9. null이랑 undefined는 저리 가! NonNullable<T>

NonNullable<T>는 아주 직관적이에요! 타입 T에서 nullundefined를 싸악 제거한 타입을 만들어줘요. 값이 무조건 있다는 것을 보장하고 싶을 때 사용하죠.

언제 쓸까요? 어떤 값이 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);
}

10. 함수 반환 타입이 뭐더라? 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가지 유틸리티 타입들, 앞으로 코드 짤 때 꼭 한번 활용해보세요! 여러분의 타입스크립트 레벨이 한 단계 더 올라갈 거예요! 👍

0개의 댓글