TypeScript 독학 가이드 9 - 유틸리티 타입

learning by Seven Fingers Studio 18분
TypeScript유틸리티타입PartialPickOmit웹개발

“이 타입의 모든 속성을 선택적으로 만들고 싶은데…” “특정 속성만 빼고 싶은데…” 이럴 때마다 새로 타입 정의하면 너무 귀찮죠. TypeScript는 이런 작업을 쉽게 해주는 유틸리티 타입들을 제공해요. 진짜 개발 생산성이 확 올라갑니다.

Partial - 모든 속성을 선택적으로

모든 속성을 optional(?)로 만들어줘요.

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

// 모든 속성이 선택적이 됨
type PartialUser = Partial<User>;

const updateUser: PartialUser = {
  name: "김철수"  // id, email, age 없어도 OK
};

console.log(updateUser);

실행 결과:

✓ 컴파일 성공
{ name: '김철수' }

필요한 속성만 전달할 수 있습니다

실전 활용: 업데이트 함수

function updateUserInfo(id: number, updates: Partial<User>): void {
  console.log(`사용자 ${id} 업데이트:`, updates);
}

updateUserInfo(1, { name: "이영희" });
updateUserInfo(2, { email: "new@example.com", age: 30 });

실행 결과:

✓ 컴파일 성공
사용자 1 업데이트: { name: '이영희' }
사용자 2 업데이트: { email: 'new@example.com', age: 30 }

업데이트할 필드만 선택적으로 전달할 수 있어 유용합니다

Required - 모든 속성을 필수로

Partial의 반대예요. 선택적 속성을 필수로 만들어줘요.

interface Config {
  apiUrl?: string;
  timeout?: number;
  retryCount?: number;
}

// 모든 속성이 필수가 됨
type RequiredConfig = Required<Config>;

const config: RequiredConfig = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  retryCount: 3
  // 하나라도 빠지면 에러!
};

console.log(config);

실행 결과:

✓ 컴파일 성공
{ apiUrl: 'https://api.example.com', timeout: 5000, retryCount: 3 }

모든 속성을 반드시 제공해야 합니다

Readonly - 모든 속성을 읽기 전용으로

수정 불가능한 객체를 만들어요.

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

const user: Readonly<User> = {
  id: 1,
  name: "김철수"
};

console.log(user);
// user.name = "이영희";  // 에러! 읽기 전용

실행 결과:

✗ 타입 에러
Cannot assign to 'name' because it is a read-only property

Readonly로 감싸면 모든 속성이 읽기 전용이 됩니다

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

필요한 속성만 골라내요.

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

// name과 email만 선택
type UserPreview = Pick<User, "name" | "email">;

const preview: UserPreview = {
  name: "김철수",
  email: "kim@example.com"
};

console.log(preview);

실행 결과:

✓ 컴파일 성공
{ name: '김철수', email: 'kim@example.com' }

선택한 속성만 포함하는 새 타입이 생성됩니다

Omit<T, K> - 특정 속성만 제외

Pick의 반대예요. 특정 속성을 빼고 나머지를 가져와요.

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

// password만 제외
type UserResponse = Omit<User, "password">;

const response: UserResponse = {
  id: 1,
  name: "김철수",
  email: "kim@example.com"
  // password 없어도 OK (빠졌으니까)
};

console.log(response);

실행 결과:

✓ 컴파일 성공
{ id: 1, name: '김철수', email: 'kim@example.com' }

민감한 정보를 제외한 응답 타입을 만들 수 있습니다

여러 속성 제외

type PublicUser = Omit<User, "password" | "email">;

const publicUser: PublicUser = {
  id: 1,
  name: "김철수"
};

console.log(publicUser);

실행 결과:

✓ 컴파일 성공
{ id: 1, name: '김철수' }

여러 속성을 동시에 제외할 수 있습니다

Record<K, T> - 키-값 쌍의 객체 타입

객체의 키와 값 타입을 지정할 수 있어요.

type Role = "admin" | "user" | "guest";

const permissions: Record<Role, string[]> = {
  admin: ["read", "write", "delete"],
  user: ["read", "write"],
  guest: ["read"]
};

console.log(permissions.admin);
console.log(permissions.user);

실행 결과:

✓ 컴파일 성공
[ 'read', 'write', 'delete' ]
[ 'read', 'write' ]

정확한 키 집합과 값 타입을 지정할 수 있습니다

페이지 설정 예제

type Page = "home" | "about" | "contact";

interface PageConfig {
  title: string;
  path: string;
}

const pages: Record<Page, PageConfig> = {
  home: { title: "홈", path: "/" },
  about: { title: "소개", path: "/about" },
  contact: { title: "연락", path: "/contact" }
};

console.log(pages.home);

실행 결과:

✓ 컴파일 성공
{ title: '홈', path: '/' }

모든 페이지가 동일한 구조를 가지도록 강제합니다

Exclude<T, U> - 유니온 타입에서 제외

유니온 타입에서 특정 타입을 제거해요.

type AllTypes = string | number | boolean;
type StringAndNumber = Exclude<AllTypes, boolean>;

const value1: StringAndNumber = "hello";
const value2: StringAndNumber = 123;
// const value3: StringAndNumber = true;  // 에러! boolean 제외됨

실행 결과:

✗ 타입 에러
Type 'boolean' is not assignable to type 'StringAndNumber'

boolean 타입이 제외되었습니다

Extract<T, U> - 유니온 타입에서 추출

Exclude의 반대예요. 특정 타입만 추출합니다.

type AllTypes = string | number | boolean;
type OnlyString = Extract<AllTypes, string>;

const str: OnlyString = "hello";
// const num: OnlyString = 123;  // 에러! string만 허용

실행 결과:

✗ 타입 에러
Type 'number' is not assignable to type 'string'

string 타입만 추출되었습니다

NonNullable - null과 undefined 제거

null과 undefined를 제외한 타입을 만들어요.

type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;

const str: DefiniteString = "hello";
// const nullStr: DefiniteString = null;       // 에러!
// const undefinedStr: DefiniteString = undefined;  // 에러!

실행 결과:

✗ 타입 에러
Type 'null' is not assignable to type 'string'

null과 undefined가 제거되었습니다

ReturnType - 함수 반환 타입 추출

함수의 리턴 타입을 가져와요.

function createUser() {
  return {
    id: 1,
    name: "김철수",
    email: "kim@example.com"
  };
}

type User = ReturnType<typeof createUser>;

const user: User = {
  id: 2,
  name: "이영희",
  email: "lee@example.com"
};

console.log(user);

실행 결과:

✓ 컴파일 성공
{ id: 2, name: '이영희', email: 'lee@example.com' }

함수 반환 타입을 재사용할 수 있습니다

Parameters - 함수 매개변수 타입 추출

함수의 매개변수 타입을 튜플로 가져와요.

function createUser(name: string, age: number, email: string) {
  return { name, age, email };
}

type CreateUserParams = Parameters<typeof createUser>;

const params: CreateUserParams = ["김철수", 25, "kim@example.com"];
const user = createUser(...params);

console.log(user);

실행 결과:

✓ 컴파일 성공
{ name: '김철수', age: 25, email: 'kim@example.com' }

함수 매개변수 타입을 배열로 재사용했습니다

실전 예제: 폼 상태 관리

유틸리티 타입을 조합해서 실용적인 타입 만들기:

interface FormData {
  username: string;
  email: string;
  password: string;
  confirmPassword: string;
  age: number;
}

// 전송용: password 제외
type FormSubmit = Omit<FormData, "confirmPassword">;

// 에러 상태: 모든 필드 선택적
type FormErrors = Partial<Record<keyof FormData, string>>;

// 읽기 전용 폼 데이터
type ReadonlyFormData = Readonly<FormData>;

const submitData: FormSubmit = {
  username: "kimcs",
  email: "kim@example.com",
  password: "1234",
  age: 25
};

const errors: FormErrors = {
  email: "이메일 형식이 잘못되었습니다",
  password: "비밀번호가 너무 짧습니다"
};

console.log("전송 데이터:", submitData);
console.log("에러:", errors);

실행 결과:

✓ 컴파일 성공
전송 데이터: { username: 'kimcs', email: 'kim@example.com', password: '1234', age: 25 }
에러: { email: '이메일 형식이 잘못되었습니다', password: '비밀번호가 너무 짧습니다' }

유틸리티 타입을 조합하여 실용적인 폼 타입을 만들었습니다

정리하면

  • Partial: 모든 속성 선택적으로
  • Required: 모든 속성 필수로
  • Readonly: 모든 속성 읽기 전용으로
  • Pick: 특정 속성만 선택
  • Omit: 특정 속성 제외
  • Record: 키-값 객체 타입
  • Exclude: 유니온에서 제외
  • Extract: 유니온에서 추출
  • NonNullable: null/undefined 제거
  • ReturnType: 함수 반환 타입 추출
  • Parameters: 함수 매개변수 타입 추출

다음 글에서는 실전 프로젝트를 만들어볼게요. TypeScript로 Todo 앱 만들고, React와 함께 사용하는 방법까지 알아봅시다!


다음 글 보기

← 이전 글
TypeScript 독학 가이드 8 - 제네릭
다음 글 →
TypeScript 독학 가이드 10 - 실전 프로젝트
← 블로그 목록으로