Kodokon kodokon.com

Literal type และ discriminated union

สร้างแบบจำลองสถานะต่าง ๆ ของแอปพลิเคชันด้วย union ที่คอมไพเลอร์ตรวจสอบให้คุณ

8 นาที · 3 คำถาม

เปิดบทเรียนนี้ใน Kodokon

literal type จำกัดค่าให้เป็นเนื้อหาที่แน่นอน: 'info' แทนที่จะเป็น string มันคือรากฐานของ API ที่ใช้ผิดไม่ได้: คอมไพเลอร์ปฏิเสธค่าใด ๆ ที่อยู่นอกรายการ และเอดิเตอร์ของคุณเสนอการเติมข้อความอัตโนมัติ

TYPESCRIPT
type Level = "info" | "warn" | "error";

function log(level: Level, message: string): void {
  console.log(`[${level}] ${message}`);
}

log("info", "Server started");
// log("fatal", "Boom"); -> rejected by the compiler
union ของ literal ล็อกค่าที่อนุญาตไว้

ขั้นตอนต่อไปคือการรวม object type ที่มีคุณสมบัติ literal ร่วมกัน เรียกว่า discriminant ตัวอย่างเช่น แต่ละสถานะของการเรียกเครือข่ายจะพกพาเฉพาะข้อมูลที่เป็นของมันเท่านั้น: การเข้าถึง data ในขณะที่กำลังโหลดเป็นสิ่งที่เป็นไปไม่ได้เลย

TYPESCRIPT
type ApiState =
  | { status: "loading" }
  | { status: "success"; data: string[] }
  | { status: "error"; message: string };

function render(state: ApiState): string {
  switch (state.status) {
    case "loading":
      return "Loading...";
    case "success":
      return state.data.join(", ");
    case "error":
      return state.message;
  }
}
union ที่แยกแยะด้วยคุณสมบัติ status

ในแต่ละ case TypeScript จะทำให้แคบลง (narrow) ตัว type: หลังจาก case 'error' แล้ว state.message มีอยู่ แต่ state.data ไม่มี กลไกนี้เรียกว่า narrowing เข้ามาแทนที่การตรวจสอบด้วยมือที่เปราะบาง เพิ่มตัวป้องกันความครบถ้วน (exhaustiveness guard) เพื่อล็อกทั้งหมดให้แน่นหนา

TYPESCRIPT
function assertNever(value: never): never {
  throw new Error("Unhandled case: " + String(value));
}

function label(state: ApiState): string {
  switch (state.status) {
    case "loading": return "In progress";
    case "success": return "Done";
    case "error": return "Failed";
    default: return assertNever(state);
  }
}
ตัวป้องกันความครบถ้วนด้วย type never

ทดสอบความรู้

ตรวจสอบว่าคุณจำประเด็นสำคัญของบทเรียนนี้ได้ครบถ้วน

  1. ใน discriminated union คุณสมบัติที่ใช้ร่วมกันเช่น status มีบทบาทอะไร?
    • ปรับปรุงประสิทธิภาพขณะรันไทม์
    • ทำให้คอมไพเลอร์ทำ type ให้แคบลงในแต่ละสาขา
    • ทำให้คุณสมบัตินี้จำเป็นต้องมีใน JSON ที่ได้รับ
    • ป้องกันคุณสมบัติที่ซ้ำกัน
  2. เมื่อมี type Level = 'info' | 'warn' การเรียก log(level, msg) แบบใดที่คอมไพล์ผ่าน?
    • log('debug', msg)
    • log('info', msg)
    • log(someString, msg) เมื่อ someString มี type เป็น string
  3. ฟังก์ชัน assertNever(value: never) ในสาขา default ของ switch มีจุดประสงค์อะไร?
    • บันทึกข้อผิดพลาดในโปรดักชัน
    • เปลี่ยน case ที่ลืมให้กลายเป็น compile error
    • แปลงค่าให้เป็น never
    • ทำให้โปรแกรมจบเร็วขึ้น