TypeScript 유틸리티 타입 소개
TypeScript는 JavaScript에 정적 타입을 더하여 코드의 안정성을 높이는 역할을 합니다.
특히 TypeScript에는 유용한 유틸리티 타입들이 있어, 코드를 작성할 때 매우 편리하게 활용할 수 있습니다.
TypeScript에서 자주 사용되는 유틸리티 타입과 기능을 소개해 보겠습니다.
1. Pick<Type, Keys>
Pick
은 특정 타입에서 원하는 프로퍼티만 선택하여 새 타입을 만드는 데 유용합니다.
이 기능을 사용하면 큰 인터페이스나 객체 타입 중에서 필요한 부분만 사용하고 싶을 때 매우 편리합니다.
언제 사용하나요?
- 특정 객체에서 필요한 일부 속성만 타입으로 사용하고자 할 때
사용 예시
interface User {
id: number;
name: string;
age: number;
email: string;
}
// User 타입에서 id와 name만 포함한 새 타입을 생성
type UserSummary = Pick<User, 'id' | 'name'>;
const user: UserSummary = {
id: 1,
name: 'Alice',
};
2. Omit<Type, Keys>
Omit
은 특정 타입에서 원하는 프로퍼티를 제외하고 새 타입을 만드는 데 사용됩니다.Pick
과는 반대로 작동하며, 타입에서 제외할 속성을 지정할 수 있습니다.
언제 사용하나요?
- 객체 타입에서 특정 프로퍼티만 제거하고 싶을 때
사용 예시
interface User {
id: number;
name: string;
age: number;
email: string;
}
// User 타입에서 email을 제외한 새 타입을 생성
type UserWithoutEmail = Omit<User, 'email'>;
const user: UserWithoutEmail = {
id: 1,
name: 'Alice',
age: 25,
};
3. Partial
Partial
은 특정 타입의 모든 프로퍼티를 선택적으로 만듭니다.
주로 객체의 일부만 업데이트할 때 유용합니다.
언제 사용하나요?
- 객체를 부분적으로 업데이트하거나 초기 값을 설정할 때
사용 예시
interface User {
id: number;
name: string;
age: number;
email: string;
};
type OptionalUser = Partial<User>
// 모든 속성이 선택적임
const updateUser: OptionalUser = {
name: 'Bob',
};
4. Required
Required
는 특정 타입의 모든 프로퍼티를 필수로 만듭니다.Partial
의 반대 기능을 한다고 볼 수 있습니다.
언제 사용하나요?
- 객체의 모든 속성이 반드시 필요할 때
사용 예시
interface User {
id?: number;
name?: string;
age?: number;
}
// 모든 속성이 필수로 바뀜
type CompleteUser = Required<User>;
5. Readonly
Readonly
는 특정 타입의 모든 프로퍼티를 읽기 전용으로 만듭니다.
이 타입을 사용하면 객체의 속성을 변경할 수 없도록 제한할 수 있습니다.
언제 사용하나요?
- 객체의 불변성을 유지하고 싶을 때
사용 예시
interface User {
id: number;
name: string;
}
const user: Readonly<User> = {
id: 1,
name: 'Alice',
};
// user.id = 2; // 오류 발생: 읽기 전용 속성입니다.
6. Record<Keys, Type>
Record
는 키와 값을 정의하여 새로운 타입을 생성합니다.
주로 객체를 미리 정의된 키의 집합으로 관리하고 싶을 때 사용합니다.
언제 사용하나요?
- 특정 키 집합과 그 값의 타입을 명확하게 정의하고 싶을 때
사용 예시
type CatInfo = { age: number; breed: string; };
type CatName = 'miffy' | 'boris' | 'mordred';
const cats: Record<CatName, CatInfo> = {
miffy: { age: 10, breed: 'Persian' },
boris: { age: 5, breed: 'Maine Coon' },
mordred: { age: 16, breed: 'British Shorthair' }
};
7. Exclude<Type, ExcludedUnion>
Exclude
는 특정 타입에서 다른 타입을 제거하여 새 타입을 만듭니다.
언제 사용하나요?
- 유니온 타입에서 특정 타입을 제외하고 싶을 때
사용 예시
type Status = 'active' | 'inactive' | 'banned';
type ActiveStatus = Exclude<Status, 'banned'>;
const status: ActiveStatus = 'active';
8. Extract<Type, Union>
Extract
는 특정 타입에서 지정된 타입만 추출합니다.
언제 사용하나요?
- 유니온 타입에서 특정 타입만 선택하고 싶을 때
사용 예시
type Status = 'active' | 'inactive' | 'banned';
type AllowedStatus = Extract<Status, 'active' | 'inactive'>;
const status: AllowedStatus = 'inactive';
9. NonNullable
NonNullable
은 특정 타입에서 null
과 undefined
를 제거합니다.
언제 사용하나요?
- 타입에서
null
또는undefined
를 허용하지 않도록 하고 싶을 때
사용 예시
type User = {
name: string | null;
age?: number;
};
type NonNullableUser = NonNullable<User['name']>; // string 타입만 남음
10. ReturnType
ReturnType
은 함수 타입의 반환 타입을 추출합니다.
주로 함수의 반환 타입을 다른 곳에서 재사용하고자 할 때 유용합니다.
언제 사용하나요?
- 함수의 반환 타입을 따로 관리하거나 재사용하고 싶을 때
사용 예시
function getUser() {
return {
id: 1,
name: 'Alice',
};
}
type User = ReturnType<typeof getUser>;
const user: User = {
id: 1,
name: 'Alice',
};
11. Parameters
Parameters
는 함수 타입의 매개변수 타입을 튜플로 반환합니다.
언제 사용하나요?
- 함수의 매개변수 타입을 다른 함수에서 재사용하고 싶을 때
사용 예시
function add(a: number, b: number): number {
return a + b;
}
type AddParams = Parameters<typeof add>; // [number, number]
const args: AddParams = [3, 5];
const result = add(...args);
커스텀 유틸리티 타입
내장된 유틸리티 타입 외에도 자주 사용하는 커스텀 유틸리티 타입들이 있습니다.
다섯 가지 유용한 커스텀 유틸리티 타입을 소개합니다.
1. Mutable
Readonly
의 반대로, 특정 타입의 모든 프로퍼티를 변경 가능하게 만듭니다.
언제 사용하나요?
- 읽기 전용 타입을 다시 변경 가능하게 하고 싶을 때
사용 예시
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
interface ReadonlyUser {
readonly id: number;
readonly name: string;
}
type MutableUser = Mutable<ReadonlyUser>;
const user: MutableUser = {
id: 1,
name: 'Alice',
};
user.id = 2; // 정상적으로 동작
2. Nullable
특정 타입에 null
을 추가하여 허용하는 타입을 만듭니다.
언제 사용하나요?
- 기존 타입에
null
을 허용하고 싶을 때
사용 예시
type Nullable<T> = T | null;
interface User {
id: number;
name: string;
}
type NullableUser = Nullable<User>;
const user: NullableUser = null; // 정상적으로 동작
3. OptionalKeys
특정 타입에서 선택적(?
)인 프로퍼티의 키만 추출합니다.
언제 사용하나요?
- 객체 타입에서 선택적인 프로퍼티만 추출하고 싶을 때
사용 예시
type User = {
id: number;
name?: string;
age?: number;
};
type OptionalKeys<T> = {
[K in keyof T]-?: undefined extends T[K] ? K : never;
}[keyof T];
type UserOptionalKeys = OptionalKeys<User>; // 'name' | 'age'
4. UnionToIntersection
유니온 타입을 인터섹션 타입으로 변환합니다.
언제 사용하나요?
- 여러 타입을 결합하여 하나의 타입으로 만들고 싶을 때
사용 예시
type UnionToIntersection<U> = (U extends any ? (arg: U) => void : never) extends (arg: infer I) => void ? I : never;
type Union = { a: string } | { b: number };
type Intersection = UnionToIntersection<Union>; // { a: string } & { b: number }
5. DeepPartial
모든 중첩된 프로퍼티를 선택적으로 만듭니다.
언제 사용하나요?
- 깊은 객체 구조에서 모든 속성을 선택적으로 만들고 싶을 때
사용 예시
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
interface User {
id: number;
profile: {
name: string;
age: number;
};
}
type PartialUser = DeepPartial<User>;
const user: PartialUser = {
profile: {
name: 'Alice',
},
};
이처럼 TypeScript의 다양한 유틸리티 타입들과 인터페이스 관련 기능들을 사용하면 코드의 가독성과 재사용성을 높이고, 타입 안전성을 강화할 수 있습니다.
Typescript의 유틸리티 타입들을 적극 활용하고, 커스텀 유틸리티 타입을 만들어 더욱 안정적이고 유지보수가 쉬운 코드를 작성해 보세요.