TypeScript 유틸리티 타입: 타입스크립트를 더 강력하게 만드는 방법
TypeScript는 JavaScript에 강력한 타입 시스템을 추가해주는 언어에요. 덕분에 코드의 안정성과 가독성을 높일 수 있죠. 하지만 복잡한 타입을 정의하거나 변환할 때 번거로울 수 있어요. 이럴 때 TypeScript가 제공하는 다양한 유틸리티 타입을 사용하면 많은 도움이 되요. 유틸리티 타입은 타입 정의를 간편하게 만들고, 코드의 재사용성을 높여줘요.
타입 유틸리티는 어떤 상황에서 유용할까요? 예를 들어, 특정 객체의 일부 속성만 수정하거나 읽기 전용으로 만들고 싶을 때, 또는 함수의 매개변수 타입을 추출해야 할 때 유용하게 사용할 수 있어요. 그럼 이제 주요 유틸리티 타입들을 살펴볼게요.
주요 유틸리티 타입들
1. Partial<T>
-
설명: T의 모든 프로퍼티를 선택적으로 만드는 타입을 구성해요. 이 유틸리티는 주어진 타입의 모든 하위 타입 집합을 나타내는 타입을 반환해요.
-
예제
interface Todo { title: string; description: string; } function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) { return { ...todo, ...fieldsToUpdate }; } const todo1 = { title: 'organize desk', description: 'clear clutter', }; const todo2 = updateTodo(todo1, { description: 'throw out trash', });
2. Readonly<T>
-
설명: T의 모든 프로퍼티를 읽기 전용(readonly)으로 설정한 타입을 구성해요. 즉, 생성된 타입의 프로퍼티는 재할당할 수 없어요.
-
예제
interface Todo { title: string; } const todo: Readonly<Todo> = { title: 'Delete inactive users', }; todo.title = 'Hello'; // 오류: 읽기 전용 프로퍼티에 재할당할 수 없어요
3. Record<K, T>
-
설명: 타입 T의 프로퍼티의 집합 K로 타입을 구성해요. 이 유틸리티는 타입의 프로퍼티들을 다른 타입에 매핑시키는 데 사용될 수 있어요.
-
예제
interface PageInfo { title: string; } type Page = 'home' | 'about' | 'contact'; const x: Record<Page, PageInfo> = { about: { title: 'about' }, contact: { title: 'contact' }, home: { title: 'home' }, };
4. Pick<T, K>
-
설명: T에서 프로퍼티 K의 집합을 선택해 타입을 구성해요.
-
예제
interface Todo { title: string; description: string; completed: boolean; } type TodoPreview = Pick<Todo, 'title' | 'completed'>; const todo: TodoPreview = { title: 'Clean room', completed: false, };
5. Omit<T, K>
-
설명: T에서 모든 프로퍼티를 선택한 다음 K를 제거한 타입을 구성해요.
-
예제
interface Todo { title: string; description: string; completed: boolean; } type TodoPreview = Omit<Todo, 'description'>; const todo: TodoPreview = { title: 'Clean room', completed: false, };
6. Exclude<T, U>
-
설명: T에서 U에 할당할 수 있는 모든 속성을 제외한 타입을 구성해요.
-
예제
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c" type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c" type T2 = Exclude<string | number | (() => void), Function>; // string | number
7. Extract<T, U>
-
설명: T에서 U에 할당할 수 있는 모든 속성을 추출하여 타입을 구성해요.
-
예제
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a" type T1 = Extract<string | number | (() => void), Function>; // () => void
8. NonNullable<T>
-
설명: T에서 null과 undefined를 제외한 타입을 구성해요.
-
예제
type T0 = NonNullable<string | number | undefined>; // string | number type T1 = NonNullable<string[] | null | undefined>; // string[]
9. Parameters<T>
-
설명: 함수 타입 T의 매개변수 타입들의 튜플 타입을 구성해요.
-
예제
declare function f1(arg: { a: number, b: string }): void type T0 = Parameters<() => string>; // [] type T1 = Parameters<(s: string) => void>; // [string] type T2 = Parameters<(<T>(arg: T) => T)>; // [unknown] type T4 = Parameters<typeof f1>; // [{ a: number, b: string }] type T5 = Parameters<any>; // unknown[] type T6 = Parameters<never>; // never type T7 = Parameters<string>; // 오류 type T8 = Parameters<Function>; // 오류
10. ConstructorParameters<T>
-
설명: 생성자 함수 타입의 모든 매개변수 타입을 가지는 튜플 타입(T가 함수가 아닌 경우 never)을 생성해요.
-
예제
type T0 = ConstructorParameters<ErrorConstructor>; // [(string | undefined)?] type T1 = ConstructorParameters<FunctionConstructor>; // string[] type T2 = ConstructorParameters<RegExpConstructor>; // [string, (string | undefined)?]
11. ReturnType<T>
-
설명: 함수 T의 반환 타입으로 구성된 타입을 만드는데요.
-
예제
declare function f1(): { a: number, b: string } type T0 = ReturnType<() => string>; // string type T1 = ReturnType<(s: string) => void>; // void type T2 = ReturnType<(<T>() => T)>; // {} type T3 = ReturnType<(<T extends U, U extends number[]>() => T)>; // number[] type T4 = ReturnType<typeof f1>; // { a: number, b: string } type T5 = ReturnType<any>; // any type T6 = ReturnType<never>; // any type T7 = ReturnType<string>; // 오류 type T8 = ReturnType<Function>; // 오류
12. InstanceType<T>
-
설명: 생성자 함수 타입 T의 인스턴스 타입으로 구성된 타입을 만들어요.
-
예제
class C { x = 0; y = 0; } type T0 = InstanceType<typeof C>; // C type T1 = InstanceType<any>; // any type T2 = InstanceType<never>; // any type T3 = InstanceType<string>; // 오류 type T4 = InstanceType<Function>; // 오류
13. Required<T>
-
설명: T의 모든 프로퍼티가 필수로 설정된 타입을 구성해요.
-
예제
interface Props { a?: number; b?: string; } const obj: Props = { a: 5 }; // 성공 const obj2: Required<Props> = { a: 5 }; // 오류: 프로퍼티 'b'가 없어요
14. ThisParameterType
-
설명: 함수 타입의 this 매개변수의 타입, 혹은 함수 타입에 this 매개변수가 없을 경우 unknown을 추출해요.
-
예제
function toHex(this: Number) { return this.toString(16); } function numberToString(n: ThisParameterType<typeof toHex>) { return toHex.apply(n); }
15. OmitThisParameter
-
설명: 함수 타입에서 ‘this’ 매개변수를 제거해요.
-
예제
function toHex(this: Number) { return this.toString(16); } const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(5); console.log(fiveToHex());
16. ThisType<T\>
-
설명: 이 유틸리티는 변형된 타입을 반환하지 않아요. 대신, 문맥적 this타입에 표시하는 역할을 해요.
-
예제
// --noImplicitThis 로 컴파일 type ObjectDescriptor<D, M> = { data?: D; methods?: M & ThisType<D & M>; // 메서드 안의 'this 타입은 D & M 입니다. } function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M { let data: object = desc.data || {}; let methods: object = desc.methods || {}; return { ...data, ...methods } as D & M; } let obj = makeObject({ data: { x: 0, y: 0 }, methods: { moveBy(dx: number, dy: number) { this.x += dx; // 강하게 타입이 정해진 this this.y += dy; // 강하게 타입이 정해진 this } } }); obj.x = 10; obj.y = 20; obj.moveBy(5, 5);
이 예제에서, makeObject의 인자로 넘겨지는 methods 객체는 ThisType<D & M>를 포함한 문맥적 타입을 가지고 있어서, methods 객체의 메서드 안에 this 타입은 { x: number, y: number } & { moveBy(dx: number, dy: number): void }에요. ThisType<T> 마커 인터페이스는 단지 lib.d.ts에 선언된 빈 인터페이스로, 객체 리터럴의 문맥적 타입으로 인식되는 것을 넘어, 그 인터페이스는 빈 인터페이스처럼 동작해요.
이렇게 다양한 유틸리티 타입을 사용하면 타입스크립트의 타입 정의를 더 유연하고 강력하게 만들 수 있어요. 이를 통해 더 견고하고 유지보수가 쉬운 코드를 작성할 수 있어요.