이 글은 Chat GPT로 TypeScript를 공부하며 정리한 글이다.
앞선 글에서 제네릭(Generic)의 개념과 기본 문법을 다뤘다면,
이번에는 그것을 기반으로 타입을 동적으로 변환·추론하는 고급 패턴을 살펴본다.
특히 keyof, Mapped Type, infer, Conditional Type을 함께 사용해
타입스크립트의 내장 유틸리티 타입(Partial, Readonly, ReturnType, Parameters)이 동작하는 원리를 직접 구현해본다.
keyof, Mapped Type, infer, Conditional Typekeyof는 객체 타입의 모든 키 이름을 문자열 리터럴 유니온으로 추출한다.
interface User {
id: number;
name: string;
age: number;
}
type UserKeys = keyof User;
// "id" | "name" | "age"
즉, keyof는 객체의 키를 타입으로 사용할 수 있게 해주며,
이를 통해 Record<keyof T, Something> 같은 구조 변환이 가능하다.
Mapped Type은 객체의 각 속성을 순회하며 새 타입을 생성하는 문법이다.
type Optional<T> = {
[K in keyof T]?: T[K];
};
[K in keyof T] → 모든 속성 순회? → 선택적(optional)으로 변환interface User {
id: number;
name: string;
}
type OptionalUser = Optional<User>;
// { id?: number; name?: string }
이것이 바로 Partial<T>의 핵심 원리다.
infer는 조건부 타입(extends ? :) 내부에서 새로운 타입 변수를 추론할 때 사용한다.
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<number>; // number
즉, infer는 “타입 내부에서 타입을 꺼내는 도구”다.
keyof, Mapped Type, infer를 조합하면
객체의 모든 하위 속성을 선택적으로 만드는 DeepPartial을 구현할 수 있다.
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
interface Post {
id: number;
title: string;
author: { name: string; email: string };
}
type PartialPost = DeepPartial<Post>;
/*
{
id?: number;
title?: string;
author?: {
name?: string;
email?: string;
};
}
*/
Readonly<T>의 동작 원리Readonly<T>도 Mapped Type으로 쉽게 구현할 수 있다.
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
interface User {
id: number;
name: string;
}
type ReadonlyUser = MyReadonly<User>;
// { readonly id: number; readonly name: string }
조건부 타입은 “타입의 분기문”이다.
T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
infer와 함께 사용하면, 타입 구조를 분석하고 특정 부분만 추출할 수 있다.
조건부 타입 내부에서 infer를 사용하면
함수의 반환 타입, 매개변수 타입 등을 자동으로 추출할 수 있다.
🔹 함수 반환 타입 추출 (ReturnType<T>)
type MyReturnType<T> =
T extends (...args: any[]) => infer R ? R : never;
type Fn = () => string;
type Result = MyReturnType<Fn>; // string
🔹 함수 매개변수 타입 추출 (Parameters<T>)
type MyParameters<T> =
T extends (...args: infer P) => any ? P : never;
type Fn = (id: number, name: string) => void;
type Params = MyParameters<Fn>; // [number, string]
function apiWrapper<T extends (...args: any[]) => any>(fn: T) {
return async (...args: Parameters<T>): Promise<ReturnType<T>> => {
console.log("요청 시작:", args);
const result = await fn(...args);
console.log("요청 완료:", result);
return result;
};
}
Parameters<T> → 인자 타입 자동 추론ReturnType<T> → 반환 타입 자동 추론즉, 원본 함수의 타입 정보를 잃지 않으면서 래핑 가능하다.
keyof, Mapped Type, infer, Conditional Type을 이해하면
TypeScript의 내장 유틸리티 타입을 직접 구현할 수 있을 뿐 아니라,
함수, 객체, 비동기 로직까지 모두 타입 안전하게 설계할 수 있다.
제네릭이 “타입의 매개변수화”라면,
이번 글에서 다룬 패턴들은 “타입의 조합과 변환”에 해당한다.
다음 글에서는 이 개념을 더 확장해 InstanceType<T>, ConstructorParameters<T> 같은
타입 추론 기반의 메타 프로그래밍 패턴을 구현해본다.