[TypeScript] 제네릭을 기반으로 한 고급 타입 변환 패턴

Chan의 기술 블로그·2025년 9월 23일

TypeScript

목록 보기
5/10

이 글은 Chat GPT로 TypeScript를 공부하며 정리한 글이다.

앞선 글에서 제네릭(Generic)의 개념과 기본 문법을 다뤘다면,
이번에는 그것을 기반으로 타입을 동적으로 변환·추론하는 고급 패턴을 살펴본다.
특히 keyof, Mapped Type, infer, Conditional Type을 함께 사용해
타입스크립트의 내장 유틸리티 타입(Partial, Readonly, ReturnType, Parameters)이 동작하는 원리를 직접 구현해본다.


keyof, Mapped Type, infer, Conditional Type

keyof — 객체의 키를 타입으로 다루기

keyof는 객체 타입의 모든 키 이름을 문자열 리터럴 유니온으로 추출한다.

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

type UserKeys = keyof User;
// "id" | "name" | "age"

즉, keyof는 객체의 키를 타입으로 사용할 수 있게 해주며,
이를 통해 Record<keyof T, Something> 같은 구조 변환이 가능하다.

Mapped Type — 타입을 반복 변환하기

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 — 타입 내부의 타입을 추론하기

infer는 조건부 타입(extends ? :) 내부에서 새로운 타입 변수를 추론할 때 사용한다.

type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type A = UnwrapPromise<Promise<string>>; // string  
type B = UnwrapPromise<number>;          // number

즉, infer는 “타입 내부에서 타입을 꺼내는 도구”다.

세 가지를 조합한 고급 패턴 — DeepPartial 구현하기

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;
  };
}
*/

내장 유틸리티 타입(Partial, Readonly, ReturnType, Parameters)이 동작 원리 이해

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 }

Conditional Type — 타입의 if문

조건부 타입은 “타입의 분기문”이다.

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 + Conditional Type — 함수 타입 분석

조건부 타입 내부에서 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]

실무 예시 — API 래퍼 함수 설계

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> 같은
타입 추론 기반의 메타 프로그래밍 패턴을 구현해본다.

profile
퍼블리셔에서 프론트앤드로 전향하기

0개의 댓글