← 가이드 목록으로 ← Back to guides

TypeScript 고급 타입과 실전 패턴 완벽 가이드 TypeScript Advanced Types & Practical Patterns Guide

TypeScript의 진짜 힘은 고급 타입 시스템에서 나옵니다. Conditional Types, Mapped Types, Template Literal Types를 활용해 더 안전하고 유연한 타입을 만들어봅니다.

TypeScript's true power comes from its advanced type system. Let's create safer and more flexible types using Conditional Types, Mapped Types, and Template Literal Types.

Conditional Types: 조건부 타입 Conditional Types

삼항 연산자처럼 조건에 따라 다른 타입을 반환합니다. 제네릭과 함께 사용하면 매우 강력합니다.

Returns different types based on conditions, like a ternary operator. Very powerful when combined with generics.

// 기본 문법: T extends U ? X : Y
type IsString<T> = T extends string ? 'yes' : 'no';

type A = IsString<'hello'>; // 'yes'
type B = IsString<42>; // 'no'

// 실전 예제: 함수 반환 타입 추출
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function getUser() { return { id: 1, name: 'Kim' }; }
type User = ReturnType<typeof getUser>; // { id: number; name: string }
// Basic syntax: T extends U ? X : Y
type IsString<T> = T extends string ? 'yes' : 'no';

type A = IsString<'hello'>; // 'yes'
type B = IsString<42>; // 'no'

// Practical example: Extract function return type
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function getUser() { return { id: 1, name: 'Kim' }; }
type User = ReturnType<typeof getUser>; // { id: number; name: string }

infer 키워드: 타입 추론 infer Keyword: Type Inference

infer는 Conditional Types 내에서 타입을 추론하고 캡처합니다.

infer infers and captures types within Conditional Types.

// 배열 요소 타입 추출
type ArrayElement<T> = T extends (infer E)[] ? E : never;

type NumberArray = ArrayElement<number[]>; // number
type StringArray = ArrayElement<string[]>; // string

// Promise 내부 타입 추출
type Awaited<T> = T extends Promise<infer U> ? U : T;

type Result = Awaited<Promise<string>>; // string

// 함수 첫 번째 파라미터 타입 추출
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;
// Extract array element type
type ArrayElement<T> = T extends (infer E)[] ? E : never;

type NumberArray = ArrayElement<number[]>; // number
type StringArray = ArrayElement<string[]>; // string

// Extract Promise inner type
type Awaited<T> = T extends Promise<infer U> ? U : T;

type Result = Awaited<Promise<string>>; // string

// Extract first parameter type of function
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;

Mapped Types: 매핑된 타입 Mapped Types

기존 타입을 기반으로 새로운 타입을 생성합니다. in keyof를 사용해 속성을 순회합니다.

Creates new types based on existing types. Uses in keyof to iterate over properties.

// 모든 속성을 선택적으로 만들기
type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

// 모든 속성을 읽기 전용으로
type MyReadonly<T> = {
  readonly [K in keyof T]: T[K];
};

// 모든 속성을 nullable로
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

// 실전: 폼 에러 타입 만들기
interface User { name: string; email: string; }
type FormErrors<T> = { [K in keyof T]?: string };
type UserErrors = FormErrors<User>;
// { name?: string; email?: string }
// Make all properties optional
type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

// Make all properties readonly
type MyReadonly<T> = {
  readonly [K in keyof T]: T[K];
};

// Make all properties nullable
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

// Practical: Create form errors type
interface User { name: string; email: string; }
type FormErrors<T> = { [K in keyof T]?: string };
type UserErrors = FormErrors<User>;
// { name?: string; email?: string }

Template Literal Types Template Literal Types

문자열 리터럴 타입을 조합하여 새로운 문자열 타입을 생성합니다.

Combines string literal types to create new string types.

// 기본 사용
type World = "world";
type Greeting = `Hello ${World}`; // "Hello world"

// 이벤트 핸들러 타입
type EventName = 'click' | 'focus' | 'blur';
type HandlerName = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"

// CSS 속성 타입
type Size = 'sm' | 'md' | 'lg';
type Color = 'primary' | 'secondary';
type ButtonClass = `btn-${Size}-${Color}`;
// "btn-sm-primary" | "btn-sm-secondary" | "btn-md-primary" | ...
// Basic usage
type World = "world";
type Greeting = `Hello ${World}`; // "Hello world"

// Event handler types
type EventName = 'click' | 'focus' | 'blur';
type HandlerName = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"

// CSS property types
type Size = 'sm' | 'md' | 'lg';
type Color = 'primary' | 'secondary';
type ButtonClass = `btn-${Size}-${Color}`;
// "btn-sm-primary" | "btn-sm-secondary" | "btn-md-primary" | ...

타입 가드 심화 Advanced Type Guards

// 사용자 정의 타입 가드
interface Bird { fly(): void; }
interface Fish { swim(): void; }

function isFish(pet: Bird | Fish): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function move(pet: Bird | Fish) {
  if (isFish(pet)) {
    pet.swim(); // Fish로 narrowing됨
  } else {
    pet.fly(); // Bird로 narrowing됨
  }
}

// in 연산자 타입 가드
function moveAlt(pet: Bird | Fish) {
  if ('swim' in pet) {
    pet.swim();
  } else {
    pet.fly();
  }
}
// Custom type guard
interface Bird { fly(): void; }
interface Fish { swim(): void; }

function isFish(pet: Bird | Fish): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function move(pet: Bird | Fish) {
  if (isFish(pet)) {
    pet.swim(); // Narrowed to Fish
  } else {
    pet.fly(); // Narrowed to Bird
  }
}

// in operator type guard
function moveAlt(pet: Bird | Fish) {
  if ('swim' in pet) {
    pet.swim();
  } else {
    pet.fly();
  }
}

실전 유틸리티 타입 만들기 Building Practical Utility Types

// DeepPartial: 중첩 객체도 선택적으로
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object
    ? DeepPartial<T[K]>
    : T[K];
};

// NonNullableKeys: null이 아닌 키만 추출
type NonNullableKeys<T> = {
  [K in keyof T]: null extends T[K] ? never : K;
}[keyof T];

// RequireAtLeastOne: 최소 하나의 속성 필수
type RequireAtLeastOne<T> = {
  [K in keyof T]-?: Required<Pick<T, K>> &
    Partial<Pick<T, Exclude<keyof T, K>>>;
}[keyof T];
// DeepPartial: Make nested objects optional too
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object
    ? DeepPartial<T[K]>
    : T[K];
};

// NonNullableKeys: Extract only non-null keys
type NonNullableKeys<T> = {
  [K in keyof T]: null extends T[K] ? never : K;
}[keyof T];

// RequireAtLeastOne: Require at least one property
type RequireAtLeastOne<T> = {
  [K in keyof T]-?: Required<Pick<T, K>> &
    Partial<Pick<T, Exclude<keyof T, K>>>;
}[keyof T];

💡 고급 타입 학습 팁 💡 Tips for Learning Advanced Types

고급 타입은 처음에 어렵게 느껴질 수 있습니다. 내장 유틸리티 타입(Partial, Pick, Omit 등)의 구현을 분석하고, 실제 프로젝트에서 타입 안전성이 필요한 부분에 적용해보세요.

Advanced types may feel difficult at first. Analyze the implementation of built-in utility types (Partial, Pick, Omit, etc.), and apply them where type safety is needed in real projects.