Kodokon kodokon.com

never, संपूर्णता, और उन्नत discriminated unions

आप अपने states को discriminated unions के साथ मॉडल करते हैं और never टाइप के साथ उनकी संपूर्णता को स्थिर कर देते हैं।

10 मिनट · 3 प्रश्न

इस पाठ को Kodokon में खोलें

never रिक्त टाइप है: इसमें कोई मान निवास नहीं करता। यह तब प्रकट होता है जब कंपाइलर सिद्ध कर देता है कि कोई कोड-पथ असंभव है - उदाहरण के लिए, किसी union के हर सदस्य को समाप्त कर देने के बाद। यह गुण इसे एक डिज़ाइन उपकरण बना देता है: यदि आप कंपाइलर को किसी switch की default शाखा में never कहलवा सकते हैं, तो आपके पास स्थिर प्रमाण है कि हर मामला संभाला गया है। यही exhaustiveness checking का आधार है।

आपने पिछले मॉड्यूल में discriminated unions की खोज की थी; अब समय है उन्हें एक वास्तविक डिज़ाइन उपकरण में बदलने का। वैकल्पिक फ़ील्ड्स से भरे एक अकेले interface की तुलना में, एक discriminated union असंभव states को अप्रस्तुत करने योग्य (unrepresentable) बना देता है: आपके पास एक साथ data और error नहीं हो सकते, या बिना data के कोई success नहीं। यह loading states, operation परिणामों, और event संदेशों के लिए सबसे उपयुक्त pattern है।

TYPESCRIPT
type FetchState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; error: Error };

function render(state: FetchState<string[]>): string {
  switch (state.status) {
    case "idle": return "Waiting";
    case "loading": return "Loading…";
    case "success": return state.data.join(", ");
    case "error": return state.error.message;
  }
}
data केवल success संस्करण में मौजूद है: और कहीं कोई पहुँच नहीं।

आप पहले ही default शाखा में call किए जाने वाले assertNever guard से रूबरू हो चुके हैं; अब आइए ठीक से विश्लेषण करें कि यह कैसे काम करता है। जब तक हर मामला कवर होता है, वहाँ इसके parameter का टाइप never होता है और सब कुछ compile हो जाता है। switch को अपडेट किए बिना union में एक संस्करण जोड़ें, और भूला हुआ संस्करण अब never को असाइन करने योग्य नहीं रहता: compile-time error, संदेश में गुमशुदा मामले के नाम के साथ। आप एक production बग को एक अस्वीकृत build में बदल देते हैं।

TYPESCRIPT
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number }
  | { kind: "rect"; width: number; height: number };

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

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle": return Math.PI * shape.radius ** 2;
    case "square": return shape.side ** 2;
    case "rect": return shape.width * shape.height;
    default: return assertNever(shape);
  }
}
Shape में एक संस्करण जोड़ना area के compilation को तोड़ देता है।

दो उन्नत सूक्ष्मताएँ आपके ध्यान की हक़दार हैं। पहली, discriminant narrowing nested संरचनाओं तक तभी फैलता है जब आप संलग्न object की सीधे जाँच करते हैं; discriminant को अकेले destructure करना यह कड़ी तोड़ देता है (संयुक्त destructuring के लिए TypeScript 4.6 से संभाले गए मामलों को छोड़कर)। दूसरी, never unions में अवशोषित हो जाता है (string | never केवल string है): यही वह चीज़ है जो conditional types को संस्करणों को छानने की अनुमति देती है, जैसा Exclude करता है।

TYPESCRIPT
type Event =
  | { type: "click"; x: number; y: number }
  | { type: "keypress"; key: string };

type EventOf<T extends Event["type"]> =
  Extract<Event, { type: T }>;

type ClickEvent = EventOf<"click">;

function handle<T extends Event["type"]>(
  type: T,
  handler: (event: EventOf<T>) => void
): void {
  /* register the handler */
}

handle("click", (e) => console.log(e.x, e.y));
Extract, union को discriminant द्वारा छानता है: handler सटीक होता है।

ज्ञान जांच

सुनिश्चित करें कि आपको इस पाठ के मुख्य बिंदु याद हैं।

  1. किसी loading state के लिए वैकल्पिक फ़ील्ड्स वाले interface की तुलना में discriminated union को क्यों प्राथमिकता दें?
    • यह रनटाइम पर कम मेमोरी का उपयोग करता है
    • यह असंगत संयोजनों (एक साथ data और error) को अप्रस्तुत करने योग्य बना देता है
    • यह switch statements लिखने से बचाता है
    • यह strict mode में अनिवार्य है
  2. यदि आप assertNever वाले switch को संशोधित किए बिना Shape में एक संस्करण जोड़ते हैं तो क्या होता है?
    • area की हर call पर एक अपवाद फेंका जाता है
    • कोड compile हो जाता है लेकिन नया संस्करण undefined लौटाता है
    • Compilation विफल हो जाती है क्योंकि भूला हुआ संस्करण never को असाइन करने योग्य नहीं है
  3. टाइप string | never क्या है?
    • never
    • string
    • unknown
    • string | undefined