TypeScript 입문 9편 - 유틸리티 타입 활용

업데이트 함수 만들 때 모든 필드 선택적으로 받고 싶은데 타입을 또 정의해야 하나 고민했어요. 그때 Partial 알게 됐는데 완전 편하더라고요. 이런 유틸리티 타입들을 제공해주니 개발 생산성이 확 올라갑니다.
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);
실행 결과:
필요한 속성만 전달할 수 있습니다
실전 활용: 업데이트 함수
function updateUserInfo(id: number, updates: Partial<User>): void {
console.log(`사용자 ${id} 업데이트:`, updates);
}
updateUserInfo(1, { name: "이영희" });
updateUserInfo(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);
실행 결과:
모든 속성을 반드시 제공해야 합니다
Readonly - 모든 속성을 읽기 전용으로
수정 불가능한 객체를 만들어요.
interface User {
id: number;
name: string;
}
const user: Readonly<User> = {
id: 1,
name: "김철수"
};
console.log(user);
// user.name = "이영희"; // 에러! 읽기 전용
실행 결과:
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);
실행 결과:
선택한 속성만 포함하는 새 타입이 생성됩니다
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);
실행 결과:
민감한 정보를 제외한 응답 타입을 만들 수 있습니다
여러 속성 제외
type PublicUser = Omit<User, "password" | "email">;
const publicUser: PublicUser = {
id: 1,
name: "김철수"
};
console.log(publicUser);
실행 결과:
여러 속성을 동시에 제외할 수 있습니다

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);
실행 결과:
정확한 키 집합과 값 타입을 지정할 수 있습니다
페이지 설정 예제
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);
실행 결과:
모든 페이지가 동일한 구조를 가지도록 강제합니다
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 제외됨
실행 결과:
boolean 타입이 제외되었습니다
Extract<T, U> - 유니온 타입에서 추출
Exclude의 반대예요. 특정 타입만 추출합니다.
type AllTypes = string | number | boolean;
type OnlyString = Extract<AllTypes, string>;
const str: OnlyString = "hello";
// const num: OnlyString = 123; // 에러! 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; // 에러!
실행 결과:
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);
실행 결과:
함수 반환 타입을 재사용할 수 있습니다
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);
실행 결과:
함수 매개변수 타입을 배열로 재사용했습니다
실전 예제: 폼 상태 관리
유틸리티 타입을 조합해서 실용적인 타입 만들기:
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);
실행 결과:
유틸리티 타입을 조합하여 실용적인 폼 타입을 만들었습니다
정리하면
- Partial: 모든 속성 선택적으로
- Required: 모든 속성 필수로
- Readonly: 모든 속성 읽기 전용으로
- Pick: 특정 속성만 선택
- Omit: 특정 속성 제외
- Record: 키-값 객체 타입
- Exclude: 유니온에서 제외
- Extract: 유니온에서 추출
- NonNullable: null/undefined 제거
- ReturnType: 함수 반환 타입 추출
- Parameters: 함수 매개변수 타입 추출
운영자 실전 노트
실제 프로젝트 진행하며 겪은 문제
- 유틸리티 타입을 모르고 수동으로 타입 복사하다가 중복 코드 양산 → Partial, Omit 등으로 간결하게 해결
- Pick과 Omit을 동시에 사용하다가 타입이 꼬여서 헷갈림 → 하나만 사용하거나 명확히 분리해야 함
이 경험을 통해 알게 된 점
- 유틸리티 타입은 TypeScript의 숨겨진 보물이다. 익숙해지면 생산성이 2배 이상 향상됨
- Partial과 Pick/Omit을 적절히 조합하면 거의 모든 타입 변환이 가능하다
다음 글에서는 실전 프로젝트를 만든다. TypeScript로 Todo 앱을 만들고, React와 함께 사용하는 방법까지 다룬다.