Back to notes
mastery-frontend-typescript
Featured

เจาะลึกระบบ Type System: Union, Intersection และ Type Narrowing

เข้าใจกลไกการทำงานของ Type Control Flow Analysis, การใช้ Discriminated Unions เพื่อจัดการ State ที่ซับซ้อน และการเลือกใช้ unknown แทน any

January 29, 20263 min read readNNexis by Seereen

บทนำ

Type ใน TypeScript ไม่ใช่แค่การแปะป้ายชื่อว่านี่คือ string หรือ number แต่มันคือ "Set theory" (ทฤษฎีเซต) เราสามารถเอา Type มารวมกัน (Union) หรือหาจุดตัด (Intersection) เพื่อสร้างข้อกำหนดข้อมูลที่ซับซ้อนและแม่นยำได้


1. Union Types (|) คือ "OR" logic

Union Type บอกว่าค่านี้เป็นได้ "อย่างใดอย่างหนึ่ง"

HLJS TYPESCRIPT
type ID = string | number; // เป็น string ก็ได้ หรือ number ก็ได้

ความเข้าใจเชิงลึก: เมื่อตัวแปรเป็น Union Type เราจะเรียกใช้ method ได้เฉพาะ ส่วนที่มีร่วมกัน เท่านั้น จนกว่าจะมีการตรวจสอบ Type (Narrowing)

HLJS TYPESCRIPT
function printId(id: string | number) {
  // ❌ Error: toUpperCase มีแค่ใน string ไม่มีใน number
  // console.log(id.toUpperCase());

  // ✅ ต้อง Narrowing ก่อน
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  }
}

2. Intersection Types (&) คือ "AND" logic

ใช้รวมคุณสมบัติของ Type เข้าด้วยกัน (เหมือนการ Merge Object)

HLJS TYPESCRIPT
interface Draggable {
  drag: () => void;
}
interface Resizable {
  resize: () => void;
}

// UIComponent ต้องทำได้ *ทั้ง* drag และ resize
type UIComponent = Draggable & Resizable;

const box: UIComponent = {
  drag: () => {},
  resize: () => {},
};

3. Discriminated Unions (Tagged Unions) 🌟

นี่คือเทคนิคขั้นสูงที่ทรงพลังที่สุดในการจัดการ State สมมติเรามี API State ที่มี 3 สถานะ: Loading, Success, Error

แทนที่จะใช้ตัวแปรแยกกัน (isLoading, error, data) ที่อาจขัดแย้งกันได้ ให้ใช้ Discriminated Union โดยมี "ตัวแบ่งแยก" (Discriminator) เช่น field kind หรือ status

HLJS TYPESCRIPT
type LoadingState = { status: "loading" };
type SuccessState = { status: "success"; data: string[] };
type ErrorState = { status: "error"; error: Error };

// State เป็นได้แค่ 3 แบบนี้เท่านั้น! เป็นแบบอื่นไม่ได้
type ApiState = LoadingState | SuccessState | ErrorState;

การใช้งาน: TS จะฉลาดมาก (Control Flow Analysis) รู้ทันทีว่าในแต่ละเคสมีตัวแปรอะไรให้ใช้บ้าง

HLJS TYPESCRIPT
function render(state: ApiState) {
  switch (state.status) {
    case "loading":
      // ตรงนี้ state คือ LoadingState
      return "Loading...";
    case "success":
      // ตรงนี้ state คือ SuccessState (เรียก .data ได้)
      return `Data: ${state.data.join(", ")}`;
    case "error":
      // ตรงนี้ state คือ ErrorState (เรียก .error ได้)
      return `Error: ${state.error.message}`;
  }
}

ทำไมวิธีนี้ถึงดีที่สุด? มันป้องกัน Invalid State เช่น isLoading: true แต่มี error โผล่มาด้วย ซึ่งเป็นบั๊กที่เจอบ่อยมากในการจัดการ State แบบแยกตัวแปร


4. any vs unknown vs never

any: ปิดตาเดิน

เมื่อใช้ any คือการบอก Compiler ว่า "เชื่อฉันเถอะ อย่ามายุ่ง" ผลคือเราเสียความปลอดภัยทั้งหมด Best Practice: ห้ามใช้ ถ้าไม่จำเป็นจริงๆ

unknown: ปิดตาแต่ต้องคลำทาง

unknown เหมือน any คือรับค่าอะไรก็ได้ แต่ ห้ามนำไปใช้งานจนกว่าจะตรวจสอบ Type

HLJS TYPESCRIPT
let value: unknown;
value = "Hello";

// ❌ Error: Object is of type 'unknown'
// console.log(value.toUpperCase());

// ✅ ต้องเช็คก่อน
if (typeof value === "string") {
  console.log(value.toUpperCase());
}

never: ทางตัน

ใช้สำหรับค่าที่ "ไม่มีวันเกิดขึ้นจริง" มักใช้ในการทำ Exhaustiveness Checking ใน Switch case

HLJS TYPESCRIPT
function unknownState(state: never): never {
  throw new Error("Unexpected state");
}

// ใน Switch case ของหัวข้อที่ 3 ถ้าเราลืม case "error"
// TS จะฟ้องว่าตัวแปร state ไม่ใช่ never (เพราะมันเหลือเคส error ที่ยังไม่ได้ดัก)

สรุป

ระบบ Type ของ TypeScript ถูกออกแบบมาให้เรา "จำลองโมเดลของข้อมูล" ได้อย่างแม่นยำ การใช้ Discriminated Unions ร่วมกับ Type Narrowing คือกุญแจสำคัญที่จะทำให้โค้ดของคุณ Solid แข็งแกร่ง ยากที่จะเกิด Runtime Error

Share this note

© 2026 My Notes by Seereen