티스토리 뷰

타입스크립트(TypeScript)를 단순한 '타입 검사기'를 넘어 '설계 도구'로 활용하게 해주는 핵심 기능이 바로 유니온(Union)과 인터섹션(Intersection)입니다. 이 두 개념을 이해하면 정적인 타입 시스템 안에서도 놀라울 만큼 유연하고 확장성 있는 아키텍처를 구축할 수 있습니다.

본 포스팅에서는 실무에서 바로 활용할 수 있는 풍부한 예제와 함께 유니온과 인터섹션의 개념을 깊이 있게 살펴보겠습니다.

1. 유니온 타입 (Union Types): "또는 (OR)"

유니온 타입은 값의 타입을 여러 개 중 하나로 허용하고 싶을 때 사용합니다. 세로 바($|$) 기호를 사용하여 정의하며, 집합론적으로는 합집합의 개념입니다.

실무 예제: 다중 상태 관리

API 요청의 상태를 정의할 때 유니온 타입은 빛을 발합니다.

type NetworkStatus = 'loading' | 'success' | 'error';

function handleResponse(status: NetworkStatus) {
  switch (status) {
    case 'loading':
      console.log("데이터를 불러오는 중입니다...");
      break;
    case 'success':
      console.log("성공적으로 완료되었습니다.");
      break;
    case 'error':
      console.log("에러가 발생했습니다.");
      break;
  }
}

함수 매개변수의 유연성

하나의 함수가 다양한 형태의 입력을 처리해야 할 때 유용합니다.

function formatId(id: string | number) {
  if (typeof id === 'string') {
    return `ID_${id.toUpperCase()}`;
  }
  return `ID_${id.toFixed(0)}`;
}

console.log(formatId("user123")); // ID_USER123
console.log(formatId(456.7));    // ID_457

2. 인터섹션 타입 (Intersection Types): "그리고 (AND)"

인터섹션 타입은 여러 타입을 하나로 결합하여 모든 타입의 기능을 갖춘 새로운 타입을 만듭니다. 앰퍼샌드($\&$) 기호를 사용하며, 집합론적으로는 교집합의 개념이지만, 객체 관점에서는 속성의 결합으로 이해하는 것이 쉽습니다.

실무 예제: 엔티티 확장

기본 사용자 정보에 권한 정보를 합쳐서 새로운 관리자 타입을 정의할 수 있습니다.

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

interface Permissions {
  canEdit: boolean;
  canDelete: boolean;
}

// User와 Permissions의 모든 속성을 가져야 함
type AdminUser = User & Permissions;

const admin: AdminUser = {
  id: "admin-01",
  name: "관리자",
  canEdit: true,
  canDelete: true
};

3. 아키텍처적 진화: 식별 가능한 유니온 (Discriminated Unions)

단순한 유니온을 넘어, 객체 내부에 공통된 '태그'를 두어 타입을 안전하게 좁히는(Narrowing) 기법입니다. 이는 현대적인 상태 관리와 리듀서(Reducer) 패턴의 근간이 됩니다.

예제: 메시지 시스템 설계

interface TextMessage {
  type: 'text'; // 식별자(Discriminant)
  content: string;
}

interface ImageMessage {
  type: 'image'; // 식별자(Discriminant)
  url: string;
  size: number;
}

interface VideoMessage {
  type: 'video'; // 식별자(Discriminant)
  url: string;
  duration: number;
}

type Message = TextMessage | ImageMessage | VideoMessage;

function renderMessage(msg: Message) {
  // 공통 속성인 'type'을 통해 타입 가드(Type Guard) 역할 수행
  switch (msg.type) {
    case 'text':
      // 여기서 msg는 TextMessage 타입으로 자동 추론됨
      console.log(`텍스트: ${msg.content}`);
      break;
    case 'image':
      // 여기서 msg는 ImageMessage 타입으로 자동 추론됨
      console.log(`이미지 링크: ${msg.url}, 크기: ${msg.size}KB`);
      break;
    case 'video':
      console.log(`비디오 재생시간: ${msg.duration}초`);
      break;
  }
}

4. 유니온과 인터섹션의 조합

때로는 이 두 가지를 혼합하여 더욱 복잡한 비즈니스 로직을 표현할 수도 있습니다.

type ResponseBase = {
  timestamp: number;
};

type SuccessResponse = ResponseBase & {
  status: 'success';
  data: string[];
};

type FailureResponse = ResponseBase & {
  status: 'failure';
  error: { code: number; message: string };
};

type ApiResponse = SuccessResponse | FailureResponse;

const handleApi = (response: ApiResponse) => {
  if (response.status === 'success') {
    console.log(response.data.length); // 안전하게 접근 가능
  } else {
    console.error(response.error.message); // 안전하게 접근 가능
  }
};

결론: 왜 유니온과 인터섹션인가?

  1. 예측 가능성: 잘못된 속성 조합이 생성되는 것을 컴파일 단계에서 방지합니다.
  2. 재사용성: 작은 인터페이스들을 조합(Composition)하여 복잡한 도메인 모델을 구축할 수 있습니다.
  3. 가독성: 코드만 보고도 해당 데이터가 어떤 상태를 가질 수 있는지 명확히 파악할 수 있습니다.

타입스크립트 아키텍처 설계에서 유니온과 인터섹션은 단순히 타입을 나열하는 수단이 아닙니다. 이는 데이터 간의 관계를 정의하고 로직의 흐름을 안전하게 가이드하는 강력한 설계 언어입니다.

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/04   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
글 보관함