유틸리티 타입은 기존 타입을 변환 해서 새로운 타입을 만드는 TypeScript 내장 도구입니다.

Partial<T> — 모든 속성을 선택적으로

TYPESCRIPT
interface User {
  name: string;
  age: number;
  email: string;
}

// 모든 속성이 optional로 변경
type PartialUser = Partial<User>;
// { name?: string; age?: number; email?: string; }

// 활용: 업데이트 함수에서 일부 필드만 받을 때
function updateUser(id: number, updates: Partial<User>): User {
  const existing: User = { name: '홍길동', age: 25, email: 'test@test.com' };
  return { ...existing, ...updates };
}

updateUser(1, { age: 26 });            // name, email은 그대로
updateUser(1, { name: '김철수' });      // age, email은 그대로

Partial의 내부 구현

TYPESCRIPT
// 실제 구현 — Mapped Type
type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

Required<T> — 모든 속성을 필수로

TYPESCRIPT
interface Config {
  host?: string;
  port?: number;
  ssl?: boolean;
}

// 모든 속성이 필수로 변경
type StrictConfig = Required<Config>;
// { host: string; port: number; ssl: boolean; }

// 활용: 기본값이 모두 채워진 최종 설정
function createConfig(overrides: Config): Required<Config> {
  return {
    host: overrides.host ?? 'localhost',
    port: overrides.port ?? 3000,
    ssl: overrides.ssl ?? false,
  };
}

Required의 내부 구현

TYPESCRIPT
type MyRequired<T> = {
  [K in keyof T]-?: T[K]; // -?로 optional을 제거
};

Pick<T, K> — 특정 속성만 선택

TYPESCRIPT
interface User {
  id: number;
  name: string;
  age: number;
  email: string;
  password: string;
}

// 필요한 속성만 골라 새 타입 생성
type UserProfile = Pick<User, 'id' | 'name' | 'email'>;
// { id: number; name: string; email: string; }

// 활용: API 응답에서 민감한 정보 제외
function getPublicProfile(user: User): Pick<User, 'id' | 'name'> {
  return { id: user.id, name: user.name };
}

Pick의 내부 구현

TYPESCRIPT
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

Omit<T, K> — 특정 속성을 제외

TYPESCRIPT
interface User {
  id: number;
  name: string;
  age: number;
  email: string;
  password: string;
}

// password를 제외한 타입
type SafeUser = Omit<User, 'password'>;
// { id: number; name: string; age: number; email: string; }

// 여러 속성 제외
type PublicUser = Omit<User, 'password' | 'email'>;
// { id: number; name: string; age: number; }

// 활용: 생성 시 id는 자동 생성되므로 제외
type CreateUserInput = Omit<User, 'id'>;

Pick vs Omit — 언제 무엇을 쓸까

TYPESCRIPT
// 속성이 많고 일부만 필요할 때 → Pick
type Summary = Pick<User, 'name' | 'email'>;

// 속성이 많고 일부만 빼야 할 때 → Omit
type WithoutPassword = Omit<User, 'password'>;

Record<K, V> — 키-값 매핑

TYPESCRIPT
// 특정 키들에 같은 타입의 값을 매핑
type Fruit = 'apple' | 'banana' | 'cherry';
type FruitInfo = Record<Fruit, { color: string; price: number }>;

const fruits: FruitInfo = {
  apple: { color: '빨강', price: 3000 },
  banana: { color: '노랑', price: 2000 },
  cherry: { color: '빨강', price: 5000 },
  // 세 과일이 모두 있어야 함
};

// 동적 키
type StringMap = Record<string, number>;
const scores: StringMap = {
  math: 90,
  english: 85,
};

Record의 내부 구현

TYPESCRIPT
type MyRecord<K extends keyof any, V> = {
  [P in K]: V;
};

유틸리티 타입 조합

실무에서는 여러 유틸리티 타입을 조합해서 사용합니다.

TYPESCRIPT
interface User {
  id: number;
  name: string;
  email: string;
  createdAt: Date;
  updatedAt: Date;
}

// 생성 입력: id와 날짜 필드 제외
type CreateUserInput = Omit<User, 'id' | 'createdAt' | 'updatedAt'>;

// 수정 입력: id 제외 + 나머지 선택적
type UpdateUserInput = Partial<Omit<User, 'id' | 'createdAt' | 'updatedAt'>>;

// 목록 표시: 핵심 정보만
type UserListItem = Pick<User, 'id' | 'name'>;

// 응답 형식
type ApiResponse<T> = {
  data: T;
  status: number;
};

type UserResponse = ApiResponse<Omit<User, 'updatedAt'>>;

Readonly<T>

TYPESCRIPT
interface Config {
  host: string;
  port: number;
}

const config: Readonly<Config> = {
  host: 'localhost',
  port: 3000,
};

// config.host = 'remote'; // ❌ Error — 읽기 전용

면접에서 자주 묻는 질문

면접에서 "Partial의 내부 구현을 설명해 보세요"라는 질문이 나옵니다.

TYPESCRIPT
// 핵심은 Mapped Types
type Partial<T> = {
  [K in keyof T]?: T[K];
};

// keyof T — T의 모든 키를 유니온으로
// K in — 각 키를 순회
// ? — 선택적으로 변환
// T[K] — 해당 키의 값 타입

정리

유틸리티역할활용
Partial<T>모든 속성을 선택적으로업데이트 입력
Required<T>모든 속성을 필수로기본값 채운 최종 설정
Pick<T, K>특정 속성만 선택API 응답 축소
Omit<T, K>특정 속성 제외민감 정보 제거
Record<K, V>키-값 매핑딕셔너리, 매핑
Readonly<T>모든 속성을 읽기 전용불변 설정
  • 유틸리티 타입의 내부 구현은 Mapped Types로 이해할 수 있다
  • 실무에서는 여러 유틸리티를 조합해서 사용하는 것이 일반적이다
댓글 로딩 중...