유니온(|)은 "이것 또는 저것", 인터섹션(&)은 "이것 ** 그리고** 저것"을 의미하는 타입 조합 연산자입니다.

유니온 타입(Union Type)

값이 ** 여러 타입 중 하나 **일 수 있을 때 사용합니다.

TYPESCRIPT
// string 또는 number
let id: string | number;
id = '홍길동';  // OK
id = 42;         // OK
// id = true;    // ❌ Error

// 함수 매개변수에서 유니온
function printId(id: string | number) {
  console.log(`ID: ${id}`);
}

유니온과 타입 좁히기

유니온 타입의 값을 사용할 때는 ** 공통 멤버 **만 접근할 수 있습니다.

TYPESCRIPT
function printId(id: string | number) {
  // ❌ string에만 있는 메서드는 바로 사용 불가
  // console.log(id.toUpperCase()); // Error

  // ✅ 타입 좁히기로 안전하게 사용
  if (typeof id === 'string') {
    console.log(id.toUpperCase()); // OK — 여기서 id는 string
  } else {
    console.log(id.toFixed(2));    // OK — 여기서 id는 number
  }
}

배열에서의 유니온

TYPESCRIPT
// (string | number)[] — 배열 안에 string 또는 number가 들어갈 수 있음
const mixed: (string | number)[] = [1, 'hello', 2, 'world'];

// string[] | number[] — string 배열이거나 number 배열
const either: string[] | number[] = [1, 2, 3]; // OK
// const wrong: string[] | number[] = [1, 'hello']; // ❌ Error

괄호 위치에 따라 의미가 완전히 달라집니다. 면접에서 이 차이를 물어보더라고요.

인터섹션 타입(Intersection Type)

여러 타입을 ** 모두 합쳐서** 하나로 만들 때 사용합니다.

TYPESCRIPT
type HasName = {
  name: string;
};

type HasAge = {
  age: number;
};

// 두 타입의 모든 속성을 가져야 함
type Person = HasName & HasAge;

const person: Person = {
  name: '홍길동',
  age: 25,
};

// ❌ 하나라도 빠지면 에러
// const wrong: Person = { name: '홍길동' }; // Error: Property 'age' is missing

인터섹션의 실전 활용

TYPESCRIPT
// API 응답에 공통 메타 정보를 추가할 때 유용
type ApiResponse = {
  status: number;
  timestamp: string;
};

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

type UserResponse = ApiResponse & UserData;
// { status: number; timestamp: string; id: number; name: string; }

유니온 vs 인터섹션 비교

TYPESCRIPT
type A = { x: number; y: number };
type B = { y: number; z: number };

// 유니온: A 또는 B (둘 중 하나를 만족)
type AorB = A | B;
const u1: AorB = { x: 1, y: 2 };       // OK (A를 만족)
const u2: AorB = { y: 2, z: 3 };       // OK (B를 만족)
const u3: AorB = { x: 1, y: 2, z: 3 }; // OK (둘 다 만족)

// 인터섹션: A 그리고 B (모두 만족해야 함)
type AandB = A & B;
const i1: AandB = { x: 1, y: 2, z: 3 }; // OK (A와 B 모두 만족)
// const i2: AandB = { x: 1, y: 2 }; // ❌ Error: z가 없음

원시 타입의 인터섹션

객체끼리의 인터섹션은 합집합처럼 동작하지만, 원시 타입끼리의 인터섹션은 다릅니다.

TYPESCRIPT
// string이면서 동시에 number인 값은 없음
type Impossible = string & number; // never

// 리터럴 타입에서의 인터섹션
type OnlyHello = string & 'hello'; // 'hello'

never가 나온다는 것은 그 타입의 값이 존재할 수 없다는 뜻입니다.

유니온과 인터섹션의 분배 법칙

타입 시스템에서도 수학의 분배 법칙이 적용됩니다.

TYPESCRIPT
// A & (B | C) = (A & B) | (A & C)
type Base = { id: number };
type TypeA = { kind: 'a'; value: string };
type TypeB = { kind: 'b'; count: number };

// 아래 두 타입은 동일
type Result1 = Base & (TypeA | TypeB);
type Result2 = (Base & TypeA) | (Base & TypeB);

실전 패턴: 유니온으로 상태 관리

TYPESCRIPT
// API 호출 상태를 유니온으로 표현
type LoadingState = { status: 'loading' };
type SuccessState = { status: 'success'; data: string[] };
type ErrorState = { status: 'error'; message: string };

type State = LoadingState | SuccessState | ErrorState;

function renderUI(state: State) {
  switch (state.status) {
    case 'loading':
      return '로딩 중...';
    case 'success':
      return state.data.join(', '); // data 접근 가능
    case 'error':
      return `에러: ${state.message}`; // message 접근 가능
  }
}

이 패턴은 Discriminated Union 이라고 하며, TypeScript에서 가장 많이 쓰이는 패턴 중 하나입니다. 자세한 내용은 Discriminated Union 편에서 다룹니다.

정리

  • 유니온(|)은 "A 또는 B" — 여러 타입 중 하나를 허용한다
  • 인터섹션(&)은 "A 그리고 B" — 모든 타입의 속성을 합친다
  • 유니온 타입은 공통 멤버만 접근 가능하며, 타입 좁히기로 분기해야 한다
  • 원시 타입의 인터섹션은 never가 될 수 있다
  • (string | number)[]string[] | number[]는 다르다
댓글 로딩 중...