블로그 목록
언어코드 품질

TypeScript 유틸리티 타입 완전 정복

TypeScript 유틸리티 타입

TypeScript는 기존 타입을 변환하거나 조합할 수 있는 유틸리티 타입을 내장으로 제공합니다. 반복적인 타입 선언을 줄이고, 타입 간의 관계를 명확하게 표현할 수 있어 코드 품질이 크게 올라갑니다.


1. Partial<T>

모든 프로퍼티를 선택적(optional) 으로 만듭니다.

interface User {
  id: number;
  name: string;
  email: string;
}

// 수정 API payload처럼, 일부 필드만 전달해도 되는 경우
function updateUser(id: number, fields: Partial<User>) {
  // fields.name만 있어도 OK
}

updateUser(1, { name: '이범희' }); // ✅

내부 구현은 다음과 같습니다.

type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

2. Required<T>

모든 프로퍼티를 필수로 만듭니다. Partial의 반대입니다.

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

// 초기화 완료 후에는 모든 값이 반드시 존재해야 한다고 선언
function startServer(config: Required<Config>) {
  console.log(`${config.host}:${config.port}`);
}

3. Readonly<T>

모든 프로퍼티를 읽기 전용으로 만듭니다. 불변성을 강제하고 싶을 때 사용합니다.

const user: Readonly<User> = {
  id: 1,
  name: '이범희',
  email: 'beomhui@promtend.com',
};

user.name = '다른이름'; // ❌ Error: 읽기 전용 속성이므로 할당 불가

배열에 적용하면 push, pop 등의 변경 메서드도 막을 수 있습니다.

const items: ReadonlyArray<string> = ['a', 'b', 'c'];
items.push('d'); // ❌ Error

4. Pick<T, K>

타입 T에서 특정 키만 골라 새로운 타입을 만듭니다.

interface Article {
  id: number;
  title: string;
  content: string;
  author: string;
  createdAt: Date;
}

// 목록 페이지에서는 content가 필요 없다
type ArticleListItem = Pick<Article, 'id' | 'title' | 'author' | 'createdAt'>;

5. Omit<T, K>

타입 T에서 특정 키를 제외한 새로운 타입을 만듭니다. Pick의 반대입니다.

// DB에서 id와 createdAt은 자동 생성 → 입력받지 않음
type CreateArticleInput = Omit<Article, 'id' | 'createdAt'>;

const input: CreateArticleInput = {
  title: '새 글',
  content: '내용...',
  author: '이범희',
};

Pick은 "이것만 남긴다", Omit은 "이것만 뺀다" 라고 기억하면 쉽습니다.


6. Record<K, V>

키 타입 K와 값 타입 V객체 맵을 만듭니다.

type Role = 'admin' | 'editor' | 'viewer';

const permissions: Record<Role, string[]> = {
  admin:  ['read', 'write', 'delete'],
  editor: ['read', 'write'],
  viewer: ['read'],
};

API 응답을 id 기준으로 인덱싱할 때도 자주 쓰입니다.

type UserMap = Record<number, User>;

const usersById: UserMap = {
  1: { id: 1, name: '이범희', email: 'a@b.com' },
  2: { id: 2, name: '홍길동', email: 'c@d.com' },
};

7. Exclude<T, U> / Extract<T, U>

유니온 타입에서 특정 멤버를 제거(Exclude) 하거나 추출(Extract) 합니다.

type Status = 'pending' | 'active' | 'banned' | 'deleted';

// banned와 deleted를 제외한 활성 상태만
type ActiveStatus = Exclude<Status, 'banned' | 'deleted'>;
// → 'pending' | 'active'

// 문자열 타입만 추출
type StringOrNumber = string | number | boolean;
type OnlyString = Extract<StringOrNumber, string>;
// → string

8. NonNullable<T>

타입에서 nullundefined를 제거합니다.

type MaybeString = string | null | undefined;

type DefinitelyString = NonNullable<MaybeString>;
// → string

function process(value: NonNullable<MaybeString>) {
  console.log(value.toUpperCase()); // null 걱정 없이 사용 가능
}

9. ReturnType<T>

함수 타입 T반환 타입을 추출합니다.

function fetchUser() {
  return { id: 1, name: '이범희' };
}

type FetchUserResult = ReturnType<typeof fetchUser>;
// → { id: number; name: string; }

라이브러리 함수의 반환 타입을 재사용할 때 특히 유용합니다.

// Redux의 useSelector 타입을 추출하는 패턴
type RootState = ReturnType<typeof store.getState>;

10. Parameters<T>

함수 타입 T매개변수 타입을 튜플로 추출합니다.

function createUser(name: string, age: number, role: Role) { /* ... */ }

type CreateUserParams = Parameters<typeof createUser>;
// → [name: string, age: number, role: Role]

// 첫 번째 파라미터 타입만 꺼내기
type FirstParam = Parameters<typeof createUser>[0];
// → string

실전 조합 패턴

유틸리티 타입은 조합해서 사용할 때 진짜 위력이 발휘됩니다.

interface Post {
  id: string;
  title: string;
  content: string;
  isDraft: boolean;
  authorId: string;
  createdAt: Date;
  updatedAt: Date;
}

// 생성 시: id, 날짜는 서버 생성, authorId는 세션에서
type CreatePostInput = Omit<Post, 'id' | 'authorId' | 'createdAt' | 'updatedAt'>;

// 수정 시: title과 content만 수정 가능하고, 모두 선택적으로
type UpdatePostInput = Partial<Pick<Post, 'title' | 'content' | 'isDraft'>>;

// 목록 응답: content 제외
type PostSummary = Omit<Post, 'content'>;

이렇게 하면 별도의 인터페이스를 여러 개 중복 선언하지 않아도 됩니다.


정리

| 유틸리티 타입 | 역할 | | --- | --- | | Partial<T> | 모든 프로퍼티를 선택적으로 | | Required<T> | 모든 프로퍼티를 필수로 | | Readonly<T> | 모든 프로퍼티를 읽기 전용으로 | | Pick<T, K> | 특정 키만 골라 새 타입 생성 | | Omit<T, K> | 특정 키를 제외한 새 타입 생성 | | Record<K, V> | 키-값 매핑 타입 생성 | | Exclude<T, U> | 유니온에서 특정 타입 제거 | | Extract<T, U> | 유니온에서 특정 타입 추출 | | NonNullable<T> | null/undefined 제거 | | ReturnType<T> | 함수 반환 타입 추출 | | Parameters<T> | 함수 파라미터 타입 추출 |

유틸리티 타입을 잘 활용하면 타입 선언의 중복이 줄어들고, 원본 타입을 수정할 때 파생 타입들이 자동으로 따라와서 유지보수가 훨씬 쉬워집니다.