본문 바로가기
프로그래밍/Typescript

[Typescript] 고급 타입 : 제네릭, 매핑 타입, 조건부 타입

by iwbap 2024. 12. 21.
728x90

들어가며

타입스크립트는 기본적인 타입 외에도 고급 타입 시스템을 제공하여 유연하고 확장성 있는 코드를 작성할 수 있도록 돕습니다. 이번 글에서는 다음 주요 개념들을 다룹니다:

  • 제네릭(Generic) : 재사용 가능한 타입 정의
  • 매핑 타입(Mapped Types) : 타입 변환 및 생성
  • 조건부 타입(Conditional Types) : 타입 수준에서의 논리 처리
    이를 통해 복잡한 데이터 구조와 다양한 상황에 적합한 타입을 다룰 수 있습니다.

1. 제네릭(Generics)

1-1. 제네릭이란 무엇인가?

제네릭은 데이터의 타입을 고정하지 않고, 사용하는 시점에서 지정할 수 있는 재사용 가능한 타입 정의입니다.

 

[typescript]

function identity<T>(value: T): T {
  return value;
}

const num = identity<number>(42); // T를 number로 지정
const str = identity<string>('Hello'); // T를 string으로 지정

 

1-2. 제네릭 인터페이스

제네릭은 인터페이스에도 적용 가능합니다.

 

[typescript]

interface Box<T> {
  content: T;
}

const stringBox: Box<string> = { content: 'Hello' };
const numberBox: Box<number> = { content: 123 };

 

1-3. 제네릭 클래스

클래스에서도 제네릭을 사용할 수 있습니다.

 

[typescript]

class KeyValueStore<K, V> {
  private store: Map<K, V> = new Map();

  set(key: K, value: V): void {
    this.store.set(key, value);
  }

  get(key: K): V | undefined {
    return this.store.get(key);
  }
}

const store = new KeyValueStore<string, number>();
store.set('age', 30);
console.log(store.get('age')); // 30

 

1-4. 제네릭 제약 (Constraints)

제네릭에 특정 조건을 부여할 수도 있습니다.

 

[typescript]

function printProperty<T, K extends keyof T>(obj: T, key: K): void {
  console.log(obj[key]);
}

const person = { name: 'Alice', age: 30 };
printProperty(person, 'name'); // Alice
// printProperty(person, 'address'); // 오류! 'address'는 T에 존재하지 않음

 

 

2. 매핑 타입(Mapped Types)

2-1. 기본 매핑 타입

매핑 타입은 기존 타입의 각 속성을 변환하여 새로운 타입을 생성합니다.

 

[typescript]

type Readonly<T> = {
  readonly [K in keyof T]: T[K];
};

type User = {
  id: number;
  name: string;
};

type ReadonlyUser = Readonly<User>;

 

2-2. 내장 유틸리티 타입 활용

타입스크립트는 다양한 매핑 타입 유틸리티를 제공합니다.

  1. Partial : 모든 속성을 선택적으로 만듭니다.
    [typescript]
    type Partial<T> = {
      [K in keyof T]?: T[K];
    };

    const user: Partial<User> = { name: 'Alice' }; // id는 없어도 OK
  2. Pick : 특정 속성만 선택합니다.
    [typescript]
    type Pick<T, K extends keyof T> = {
      [P in K]: T[P];
    };

    type UserNameOnly = Pick<User, 'name'>;


  3. Omit : 특정 속성을 제외합니다.
    [typescript]
    type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

    type UserWithoutId = Omit<User, 'id'>;


  4. Record : 모든 키에 특정 타입을 매핑합니다.
    [typescript]
    type Record<K extends keyof any, T> = {
      [P in K]: T;
    };

    type Config = Record<'host' | 'port', string>;

3. 조건부 타입(Conditional Types)

3-1. 기본 문법

조건부 타입은 삼항 연산자(T extends U ? X : Y) 형태로 사용됩니다.

 

[typescript]

type IsString<T> = T extends string ? 'Yes' : 'No';

type A = IsString<string>; // 'Yes'
type B = IsString<number>; // 'No'

 

3-2. 분배적 조건부 타입

유니온 타입에서 조건부 타입은 각 요소에 개별적으로 적용됩니다.

 

[typescript]

type Exclude<T, U> = T extends U ? never : T;

type Result = Exclude<'a' | 'b' | 'c', 'a' | 'c'>;
// Result = 'b'

 

3-3. 활용 예제

조건부 타입을 활용하면 복잡한 타입 로직을 간결하게 처리할 수 있습니다.

 

[typescript]

type Flatten<T> = T extends any[] ? T[number] : T;

type A = Flatten<number[]>; // number
type B = Flatten<string>; // string

 

 

4. 실용 예제 : 고급 타입 활용

문제

전자상거래 시스템에서 상품 데이터를 관리한다고 가정합니다. 상품은 다음과 같은 구조를 가집니다:

  • id : 상품 ID (숫자)
  • name : 상품 이름 (문자열)
  • details : 선택적 세부 정보 (객체)

데이터를 처리하는 함수를 작성하며, 고급 타입을 사용해 코드를 안전하고 유연하게 작성합니다.

코드

[typescript]

type Product = {
  id: number;
  name: string;
  details?: {
    description: string;
    price: number;
  };
};

type ProductSummary = Pick<Product, 'id' | 'name'>;

function getProductSummary(product: Product): ProductSummary {
  const { id, name } = product;
  return { id, name };
}

function printProductDetails<T extends Product>(product: T): void {
  console.log(`Product: ${product.name}`);
  if (product.details) {
    console.log(`Price: $${product.details.price}`);
  }
}

const product: Product = {
  id: 1,
  name: 'Laptop',
  details: {
    description: 'A powerful laptop',
    price: 1500,
  },
};

console.log(getProductSummary(product));
printProductDetails(product);

 

 

마무리

이번 글에서는 타입스크립트의 고급 타입인 제네릭, 매핑 타입, 조건부 타입을 학습했습니다. 이 기능들은 복잡한 데이터 구조를 처리하거나 재사용 가능한 타입 로직을 작성할 때 매우 유용합니다. 고급 타입을 활용하면 코드의 안전성과 가독성을 동시에 확보할 수 있습니다.

다음 단계에서는 모듈과 네임스페이스, 선언 병합을 다루어, 대규모 코드베이스에서도 유지보수와 확장성을 고려한 구조화 방법을 학습하겠습니다.

728x90