유니온과 인터섹션 — 파이프와 앰퍼샌드로 타입 조합하기
유니온(
|)은 "이것 또는 저것", 인터섹션(&)은 "이것 ** 그리고** 저것"을 의미하는 타입 조합 연산자입니다.
유니온 타입(Union Type)
값이 ** 여러 타입 중 하나 **일 수 있을 때 사용합니다.
// string 또는 number
let id: string | number;
id = '홍길동'; // OK
id = 42; // OK
// id = true; // ❌ Error
// 함수 매개변수에서 유니온
function printId(id: string | number) {
console.log(`ID: ${id}`);
}
유니온과 타입 좁히기
유니온 타입의 값을 사용할 때는 ** 공통 멤버 **만 접근할 수 있습니다.
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
}
}
배열에서의 유니온
// (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)
여러 타입을 ** 모두 합쳐서** 하나로 만들 때 사용합니다.
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
인터섹션의 실전 활용
// 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 인터섹션 비교
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가 없음
원시 타입의 인터섹션
객체끼리의 인터섹션은 합집합처럼 동작하지만, 원시 타입끼리의 인터섹션은 다릅니다.
// string이면서 동시에 number인 값은 없음
type Impossible = string & number; // never
// 리터럴 타입에서의 인터섹션
type OnlyHello = string & 'hello'; // 'hello'
never가 나온다는 것은 그 타입의 값이 존재할 수 없다는 뜻입니다.
유니온과 인터섹션의 분배 법칙
타입 시스템에서도 수학의 분배 법칙이 적용됩니다.
// 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);
실전 패턴: 유니온으로 상태 관리
// 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[]는 다르다
댓글 로딩 중...