ข้ามไปยังเนื้อหา

Unions & Intersections

union A | B อธิบาย value ที่เป็น ไม่ A ก็ B ใน model เซตนี่คือ set union — union จึง กว้างกว่า สมาชิกของตัวเองเสมอ

type Id = string | number;
let id: Id = "abc";
id = 123; // ✅ ทั้งคู่เป็นสมาชิกของ union

จุดที่ต้องระวัง: ระหว่างที่คุณถือ union อยู่ คุณแตะได้แค่ member ที่ ทุก branch มีร่วมกัน เพราะ compiler ยังไม่รู้ว่าคุณถือตัวไหน

function printId(id: string | number) {
id.toUpperCase();
// ❌ toUpperCase ไม่มีบน number
// ตรงนี้ใช้ได้แค่ member ที่ string และ number มีร่วมกัน
}

จะใช้ member เฉพาะ type ต้อง narrow ก่อน — เนื้อหาทั้งบทหน้า จนกว่าจะ narrow union จะเปิดให้เห็นแค่ surface ที่มีร่วมกัน

intersection A & B อธิบาย value ที่เป็นทั้ง A และ B พร้อมกัน สำหรับ object type นี่หมายถึง “มีทุก property ของทั้งสอง” — intersection ของ object จึง แคบกว่า (เงื่อนไขมากขึ้น value ที่ valid น้อยลง)

type WithId = { id: string };
type WithTimestamps = { createdAt: Date; updatedAt: Date };
type Entity = WithId & WithTimestamps;
// ต้องมี id และ createdAt และ updatedAt
const e: Entity = { id: "1", createdAt: new Date(), updatedAt: new Date() };

นี่คือวิธี idiomatic ในการประกอบ object type จากชิ้นเล็ก ๆ ที่ reuse ได้ — ยืดหยุ่นกว่า inheritance มาก เพราะผสมได้ทุกแบบ

flowchart TB
  subgraph un["A OR B  (union)"]
    u["more values allowed
fewer members guaranteed"]
  end
  subgraph inter["A AND B  (intersection)"]
    i["fewer values allowed
more members guaranteed"]
  end
union กว้างขึ้น intersection แคบลง

สังเกต trade-off ของแต่ละทิศ:

  • union อนุญาต value มากขึ้น แต่การันตี member ที่แตะได้อย่างปลอดภัยน้อยลง (แค่ที่มีร่วมกัน)
  • intersection อนุญาต value น้อยลง แต่การันตี member มากขึ้น (ครบทุกตัว)
  • string & number เป็น never — ไม่มี value ไหนเป็นทั้งสอง intersection เลยเป็นเซตว่าง
  • union กับ any จะยุบเป็น any; union กับ never จะทิ้ง never ไป (T | never ก็คือ T เพราะการเพิ่มเซตว่างไม่เปลี่ยนอะไร)
  • intersect object type ที่มี property primitive ชนกัน อาจได้ never สำหรับ property นั้น
type A = { kind: "a"; value: number };
type B = { kind: "b"; value: string };
type Both = A & B;
// kind: "a" & "b" → never; type นี้สร้างจริงไม่ได้

ตัวสุดท้ายเป็นสัญญาณ: เวลาคุณอยากได้ “หนึ่งใน object shape พวกนี้” คุณเกือบทุกครั้งต้องการ union (A | B) ที่มี discriminant field ร่วมกัน — ไม่ใช่ intersection นั่นคือ discriminated-union pattern ในบท narrowing

ระหว่างถือ value type `string | number` คุณแตะ member ไหนได้โดยไม่ต้อง narrow?
intersection ของ object type สองตัว `A & B` ต้องการอะไร?
อันไหนกว้างกว่า (อนุญาต value มากกว่า): union หรือ intersection?
คุณอยากได้ "value ที่เป็นหนึ่งใน object shape หลายแบบ" ควรหยิบอะไรมาใช้?