TypeScript Utility Types Partial Pick Omit Record 실전

··설명 약 8분·작성: 해비·블로그 소개

TypeScript의 Partial, Pick, Omit, Record는 각각 언제 사용하나요?

기존 객체에서 입력용 타입을 만들 때 Pick은 쓸 필드, Omit은 뺄 필드, Partial은 생략할 수 있는 필드를 정합니다. Record는 정해진 키에 대응하는 값의 타입을 표현합니다.

본문 근거: TypeScript Utility Types 공식 설명

직접 확인: 오류에서 수정까지

TypeScript Utility Types Partial Pick Omit Record 실전 · 독립 실습

얻을 결과: Pick<Task, 'title'>을 사용해 수정 입력의 범위를 명시합니다.

예제 크게 보기 (새 탭)

단독 검사는 tsconfig.json이 없는 새 폴더에서 example.ts 한 파일씩 교체하며 진행합니다. TypeScript가 없다면 설치 글에서 Node.js/npm·TypeScript 준비를 확인하세요. 타입 검사 성공은 코드 실행 성공과 구분합니다.

독자 질문: Task에서 편집 가능한 필드만 별도 타입으로 어떻게 뽑을까요?

얻을 결과: Pick<Task, ‘title’>을 사용해 수정 입력의 범위를 명시합니다.

오류 코드를 별도 example.ts에 복사합니다.

type Task = { id: string; title: string; completed: boolean };
type Edit = Pick<Task, 'titel'>;

예상 타입 오류 (TS2344): ‘titel’은 keyof Task 조건에 맞지 않습니다. 실제 키 title로 수정합니다.

위 코드를 다음 수정 예제로 교체합니다.

type Task = { id: string; title: string; completed: boolean };
type Edit = Partial<Pick<Task, 'title'>>;
const update: Edit = { title: '수정' };
console.log(update);
npx tsc --strict --noEmit example.ts

기존 최소 예제 확인 기록: TypeScript 7.0.2의 별도 최소 예제에서 오류 TS2344, 수정 후 exit 0 확인. TaskFlow 전체 체크포인트·브라우저 실행은 미확인.

다음 학습: 13회차에서 각 속성을 같은 규칙으로 바꾸는 Mapped Type을 만듭니다.

Partial

모든 property를 optional로 만듭니다.

type Task = {
  id: string;
  title: string;
  completed: boolean;
};


type TaskPatch =
  Partial<Task>;

TaskPatch:

id?
title?
completed?

부분 수정 payload에 사용할 수 있습니다.

하지만 id까지 수정 가능해지는 것이 실제 도메인 규칙과 맞는지 확인해야 합니다.

Omit

일부 property를 제거합니다.

type CreateTaskInput =
  Omit<
    Task,
    'id' | 'completed'
  >;

결과:

type CreateTaskInput = {
  title: string;
};

id는 서버/클라이언트에서 생성하고 completed 기본값은 false라면 적절합니다.

Pick

필요한 property만 선택합니다.

type TaskPreview =
  Pick<
    Task,
    'id' | 'title'
  >;

목록 미리보기 등 작은 구조에 사용할 수 있습니다.

Partial + Pick

수정 가능한 필드만 optional하게 만들 수 있습니다.

type UpdateTaskInput =
  Partial<
    Pick<
      Task,
      'title' |
      'completed'
    >
  >;

id가 수정 대상에서 제외됩니다.

Record

key union 전체에 동일한 value 타입을 적용합니다.

type TaskFilter =
  | 'all'
  | 'active'
  | 'completed';


const labels:
  Record<
    TaskFilter,
    string
  > = {
    all: '전체',
    active: '진행 중',
    completed: '완료',
  };

key 하나를 빼면 오류가 발생합니다.

Utility Type을 중첩할 때 읽기

다음 타입은 정확하지만 길 수 있습니다.

type UpdateTaskInput =
  Partial<
    Pick<
      Task,
      'title' |
      'completed'
    >
  >;

의미 있는 이름을 붙이면 사용하는 쪽은 단순해집니다.

모든 타입을 파생할 필요는 없습니다

도메인 의미가 Task와 완전히 다르다면 별도 타입을 직접 정의하는 편이 읽기 쉬울 수 있습니다.

예를 들어 외부 API 생성 payload와 내부 Task shape가 시간이 지나며 크게 달라질 수 있다면 명시적으로 정의할 수 있습니다.

타입 중복 제거와 도메인 경계 명확성 사이에서 판단합니다.

Required

optional property를 모두 required로 바꿉니다.

type Draft = {
  title?: string;
};


type CompleteDraft =
  Required<Draft>;

실제 validation 이후 완성 상태를 나타낼 때 활용할 수 있습니다.

Pick과 Omit은 타입의 허용 모양을 바꾸며 실제 객체에서 속성을 삭제하거나 새 객체를 만들지 않습니다. 외부 입력의 불필요한 필드를 제거하고 빈 수정 요청을 거부하는 정책은 실행 코드에서 따로 구현합니다.

원리 확인: TypeScript Utility Types 공식 설명 (2026-10-12). 아래 기대 결과와 기존 실행 기록은 구분해서 읽습니다.

학습 완료 체크리스트

생성 입력에서 제외할 필드, 수정할 수 있는 필드, 목록에 보여 줄 필드를 각각 정해 보세요. Partial<Task>만 쓰면 id까지 수정 후보가 되는 이유와 Partial<Pick<Task, …>>의 차이를 설명할 수 있어야 합니다.


작은 변형으로 확인하기

update를 빈 객체로 바꾸면 Partial 때문에 통과합니다. id를 넣으면 거절됩니다. 빈 수정 요청을 허용할지 여부는 별도 실행 시 정책입니다.

선택 실습: 할 일 앱에 적용하기

TaskFlow는 작업을 추가·완료·삭제하는 할 일 앱입니다. 이 선택 실습에서는 JavaScript 코드를 TypeScript로 확장합니다.

Task를 기준으로 생성·수정·목록용 타입을 파생합니다.

선택: 연재 프로젝트와 전체 자료

연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.

TaskFlow 프로젝트 적용

TypeScript Utility Types Partial Pick Omit Record 실전 · 단계별 TaskFlow

타입을 적용한 TaskFlow에서 작업을 추가·완료 전환·삭제하세요. 타입만 바뀐 단계는 이전과 화면이 같습니다.

예제 크게 보기 (새 탭)

시작·완료 파일과 적용 순서에서 이번 변경 파일의 전체 내용과 적용 순서를 확인합니다.

시작본과 이번 변경

TypeScript 제네릭 제약과 keyof 안전한 helper 만들기의 완료 체크포인트를 기준으로 이어갑니다. 변경하지 않는 파일은 그 시작 상태를 유지합니다.

src/types/task.ts에서 생성·수정·미리보기 타입을 파생하고, 이번 완료본의 src/core/edit.ts에서 생성·수정 helper가 import type으로 사용합니다. 기존 src/core/tasks.ts의 CRUD는 시작본을 유지합니다. updateTask에 id를 넣으면 거절하고 title만 넣으면 통과하는지 검사합니다.

이전 체크포인트

typescript-step-11-generic-constraints

이번 목표

Task를 기준으로 생성·수정·목록용 타입을 파생합니다.

전체 파일·코드 보기

src/types/task.ts

export type Task = {
  id: string;
  title: string;
  completed: boolean;
};


export type CreateTaskInput =
  Omit<
    Task,
    'id' |
    'completed'
  >;


export type UpdateTaskInput =
  Partial<
    Pick<
      Task,
      'title' |
      'completed'
    >
  >;


export type TaskPreview =
  Pick<
    Task,
    'id' |
    'title'
  >;

create 함수

export function createTask(
  input: CreateTaskInput,
): Task {
  return {
    id:
      crypto.randomUUID(),
    title: input.title,
    completed: false,
  };
}

update 함수

export function updateTask(
  task: Task,
  input: UpdateTaskInput,
): Task {
  return {
    ...task,
    ...input,
  };
}

검증

npx tsc --noEmit

id를 UpdateTaskInput에 넣으면 오류가 발생하는지 확인합니다.

중간부터 시작하는 전체 파일과 적용 순서

이번 글 시작 ZIP · 이번 글 완료 ZIP

시작 ZIP은 직전 프로젝트 단계 typescript-step-11-generic-constraints의 실제 완료 파일입니다. 프로젝트 순서를 이어가는 기준이며 사이트의 기술 읽기 순서와는 구분합니다.

  1. 시작 ZIP을 새 폴더에 풉니다. 이 폴더가 작업 루트이며 기존 체크포인트 파일을 별도로 찾을 필요가 없습니다.
  2. Task 입력·수정·미리보기 타입과 create/update 함수를 추가합니다. 기존 CRUD 파일은 유지합니다.
  3. 아래 변경 파일을 같은 상대 경로에 추가하거나 전체 교체합니다. 표시하지 않은 파일과 images는 시작본 그대로 유지합니다. package-lock.json은 완료 ZIP에 있는 파일을 함께 적용합니다.
  4. 작업 루트에서 명령을 실행하고 예상 결과를 확인합니다. 완료 ZIP은 직접 적용한 결과를 비교하는 기준입니다.

추가·교체 파일

  • CHANGES.md — 전체 교체
  • README.md — 전체 교체
  • src/types/task.ts — 전체 교체
  • src/core/edit.ts — 추가

실행과 확인

npm ci
npm run dev
npm run typecheck
npm test
npm run build

개발 서버는 실행 상태로 두고 다른 터미널에서 검사 명령을 실행하세요. Node.js 24.18.0에서 검증했습니다. JavaScript는 http://127.0.0.1:4173, TypeScript는 Vite 실행 출력의 주소로 접속합니다.

예상 결과: 생성 시 id 자동 생성·completed=false, 부분 수정은 원본을 변경하지 않습니다.

기존 ZIP 검증 기록: TypeScript strict 검사·긍정/부정 타입 계약·런타임 검사·Vite 빌드 통과. 시작 ZIP에 이 글의 변경 파일만 적용한 결과와 완료 ZIP의 모든 파일 내용이 일치했습니다. 브라우저 화면 배치·실제 기기·외부 CDN 가용성은 이 검사에 포함하지 않았습니다.

이 절의 전체 파일은 ZIP과 일치합니다. 위쪽 원문의 짧은 예제나 생략 표시가 있는 부분 코드는 개념·변경 위치 설명용입니다. 서버 저장과 인증은 제공하지 않으며 JSON fixture는 로컬 읽기 실습용입니다.

복사할 변경 파일 전체 코드

src/types/task.ts — 전체 파일

export type Task = {
  id: string;
  title: string;
  completed: boolean;
};


export type CreateTaskInput =
  Omit<
    Task,
    'id' |
    'completed'
  >;


export type UpdateTaskInput =
  Partial<
    Pick<
      Task,
      'title' |
      'completed'
    >
  >;


export type TaskPreview =
  Pick<
    Task,
    'id' |
    'title'
  >;
export type { TaskFilter } from '../constants/task.js';

src/core/edit.ts — 전체 파일

import type { Task, CreateTaskInput, UpdateTaskInput } from '../types/task.js';
export function createTask(
  input: CreateTaskInput,
): Task {
  return {
    id:
      crypto.randomUUID(),
    title: input.title,
    completed: false,
  };
}
export function updateTask(
  task: Task,
  input: UpdateTaskInput,
): Task {
  return {
    ...task,
    ...input,
  };
}

완료 체크포인트

typescript-step-12-utility-types

다음 학습

다음 글에서는 keyof를 반복 순회해 객체 property를 규칙적으로 변환하는 Mapped Type을 배웁니다.

다음 글: TypeScript Mapped Type 객체 타입을 기반으로 새 타입 만들기

글 소개·학습 준비·전체 목차
TypeScript Utility Types Partial Pick Omit Record 실전

TypeScript · 12 / 22 · 필수 학습 · Utility Types

생성·수정·미리보기 타입에 어떤 Utility Type을 고를까요? Pick은 사용할 필드를 선택하고 Omit은 제외할 필드를 지정하며 Partial은 선택한 필드를 생략 가능하게 만듭니다. Record는 정해진 키의 값 지도를 만듭니다. 수정 입력에서 id를 제외하는 예제로 조합의 목적을 확인합니다.

이번 글에서 배우는 내용

  • Partial
  • Pick
  • Omit
  • Record
  • Required 기본 개념
  • 타입 파생
  • 도메인 의미가 사라지지 않게 이름 붙이는 기준

단독 학습 선수 지식

객체 타입과 keyof·기본 제네릭을 알면 됩니다. 이전 TaskFlow 파일 없이 직접 확인 예제로 학습할 수 있습니다.

목차

이 글이 도움이 되었나요?

조회 중

읽음 기록

읽음 — / 전체 22개 · 필수 21개

이 글을 쓴 해비

웹사이트를 제작·운영하고, 개인 서비스와 자동화·AI 도구를 만듭니다. 직접 만들며 배운 내용과 문제 해결 과정을 BlogFlow에 기록합니다.

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기