Ch 05. 유니온과 인터섹션
|와 & 두 연산자로 타입을 조합합니다. 판별 유니온 패턴까지 맛봅니다.
유니온 타입 (|)
"이 타입 또는 저 타입" — 합집합입니다.
// 파일: src/union-basic.tstype StringOrNumber = string | number;function stringify(value: StringOrNumber): string { return String(value);}stringify("hello"); // OKstringify(42); // OKstringify(true); // Error: Argument of type 'boolean' is not assignable// null 허용 패턴type Nullable<T> = T | null;type MaybeString = Nullable<string>; // string | nullfunction getUsername(id: number): MaybeString { return id === 0 ? null : "Alice";}
유니온 타입 좁히기 (맛보기)
유니온 타입은 공통 프로퍼티만 직접 접근할 수 있습니다.
// 파일: src/union-narrowing.tsfunction formatId(id: string | number): string { // id.toFixed() // Error — string에는 toFixed가 없음 // id.toUpperCase() // Error — number에는 없음 // typeof로 좁히기 if (typeof id === "string") { return id.toUpperCase(); // OK — string 확정 } return id.toFixed(0); // OK — number 확정}
타입 좁히기 전체는 PART 03에서 다룹니다.
리터럴 타입
특정 값 자체가 타입이 됩니다.
// 파일: src/literal-types.tstype Direction = "north" | "south" | "east" | "west";type Coin = 1 | 5 | 10 | 50 | 100 | 500;type Toggle = true | false; // boolean과 동일function move(dir: Direction, steps: number): void { console.log(`moving ${dir} by ${steps}`);}move("north", 3); // OKmove("up", 3); // Error: Argument of type '"up"' is not assignable to type 'Direction'// 함수 반환 타입으로도 활용function compare(a: number, b: number): -1 | 0 | 1 { if (a < b) return -1; if (a > b) return 1; return 0;}
인터섹션 타입 (&)
"이 타입이면서 저 타입" — 교집합이 아니라 모든 프로퍼티를 합칩니다.
// 파일: src/intersection.tsinterface HasName { name: string;}interface HasAge { age: number;}interface HasEmail { email: string;}type Person = HasName & HasAge;type Contact = HasName & HasEmail;type FullProfile = HasName & HasAge & HasEmail;const person: Person = { name: "Alice", age: 30 };const contact: Contact = { name: "Bob", email: "[email protected]" };const profile: FullProfile = { name: "Carol", age: 25, email: "[email protected]",};
인터섹션은 믹스인 패턴이나 여러 인터페이스를 합칠 때 씁니다.
인터섹션 실무 패턴 — Mixin
// 파일: src/intersection-mixin.ts// 공통 응답 메타데이터type ApiMeta = { requestId: string; timestamp: number;};// 각 엔드포인트별 데이터type UserData = { id: number; name: string;};type PostData = { id: number; title: string; content: string;};// API 응답 = 데이터 + 공통 메타type UserResponse = UserData & ApiMeta;type PostResponse = PostData & ApiMeta;function wrapResponse<T>(data: T): T & ApiMeta { return { ...data, requestId: crypto.randomUUID(), timestamp: Date.now(), };}
판별 유니온 (Discriminated Union) 맛보기
서로 다른 형태의 객체를 하나의 타입으로 다룰 때 핵심 패턴입니다.
// 파일: src/discriminated-union.ts// 공통 판별자(discriminant) 프로퍼티를 가진 타입들interface Circle { kind: "circle"; // 리터럴 타입 radius: number;}interface Rectangle { kind: "rectangle"; width: number; height: number;}interface Triangle { kind: "triangle"; base: number; height: number;}type Shape = Circle | Rectangle | Triangle;function area(shape: Shape): number { switch (shape.kind) { case "circle": return Math.PI * shape.radius ** 2; // shape: Circle case "rectangle": return shape.width * shape.height; // shape: Rectangle case "triangle": return (shape.base * shape.height) / 2; // shape: Triangle }}console.log(area({ kind: "circle", radius: 5 })); // ~78.5console.log(area({ kind: "rectangle", width: 4, height: 6 })); // 24
kind 같은 리터럴 타입 프로퍼티가 판별자 역할을 합니다. TypeScript는 switch의 각 case에서 정확한 타입을 알게 됩니다.
판별 유니온 심화 (완전성 검사, exhaustive check)는 PART 03에서 다룹니다.
유니온 vs 인터섹션 비교
// 파일: src/union-vs-intersection.tstype A = { x: number };type B = { y: string };// 유니온 — A이거나 B (공통 프로퍼티만 접근 가능)type AorB = A | B;const v1: AorB = { x: 1 }; // OKconst v2: AorB = { y: "hi" }; // OKconst v3: AorB = { x: 1, y: "hi" }; // OK (A와 B 모두 만족)// v1.x — 안전하지 않음, v1이 B일 수도 있으므로// 인터섹션 — A이면서 B (모든 프로퍼티 접근 가능)type AandB = A & B;const v4: AandB = { x: 1, y: "hi" }; // 반드시 둘 다 있어야 함console.log(v4.x, v4.y); // OK
정리
|— 여러 타입 중 하나, 공통 프로퍼티만 바로 접근 가능합니다.&— 모든 타입의 프로퍼티를 합침, 믹스인에 유용합니다.- 리터럴 타입은 값 자체를 타입으로 사용합니다.
- 판별 유니온은
kind같은 공통 리터럴 프로퍼티로 타입을 좁힙니다.