आप अपने states को discriminated unions के साथ मॉडल करते हैं और never टाइप के साथ उनकी संपूर्णता को स्थिर कर देते हैं।
इस पाठ को Kodokon में खोलेंnever रिक्त टाइप है: इसमें कोई मान निवास नहीं करता। यह तब प्रकट होता है जब कंपाइलर सिद्ध कर देता है कि कोई कोड-पथ असंभव है - उदाहरण के लिए, किसी union के हर सदस्य को समाप्त कर देने के बाद। यह गुण इसे एक डिज़ाइन उपकरण बना देता है: यदि आप कंपाइलर को किसी switch की default शाखा में never कहलवा सकते हैं, तो आपके पास स्थिर प्रमाण है कि हर मामला संभाला गया है। यही exhaustiveness checking का आधार है।
आपने पिछले मॉड्यूल में discriminated unions की खोज की थी; अब समय है उन्हें एक वास्तविक डिज़ाइन उपकरण में बदलने का। वैकल्पिक फ़ील्ड्स से भरे एक अकेले interface की तुलना में, एक discriminated union असंभव states को अप्रस्तुत करने योग्य (unrepresentable) बना देता है: आपके पास एक साथ data और error नहीं हो सकते, या बिना data के कोई success नहीं। यह loading states, operation परिणामों, और event संदेशों के लिए सबसे उपयुक्त pattern है।
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;
}
}आप पहले ही default शाखा में call किए जाने वाले assertNever guard से रूबरू हो चुके हैं; अब आइए ठीक से विश्लेषण करें कि यह कैसे काम करता है। जब तक हर मामला कवर होता है, वहाँ इसके parameter का टाइप never होता है और सब कुछ compile हो जाता है। switch को अपडेट किए बिना union में एक संस्करण जोड़ें, और भूला हुआ संस्करण अब never को असाइन करने योग्य नहीं रहता: compile-time error, संदेश में गुमशुदा मामले के नाम के साथ। आप एक production बग को एक अस्वीकृत build में बदल देते हैं।
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);
}
}दो उन्नत सूक्ष्मताएँ आपके ध्यान की हक़दार हैं। पहली, discriminant narrowing nested संरचनाओं तक तभी फैलता है जब आप संलग्न object की सीधे जाँच करते हैं; discriminant को अकेले destructure करना यह कड़ी तोड़ देता है (संयुक्त destructuring के लिए TypeScript 4.6 से संभाले गए मामलों को छोड़कर)। दूसरी, never unions में अवशोषित हो जाता है (string | never केवल string है): यही वह चीज़ है जो conditional types को संस्करणों को छानने की अनुमति देती है, जैसा Exclude करता है।
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));assertNever वाले switch को संशोधित किए बिना Shape में एक संस्करण जोड़ते हैं तो क्या होता है?string | never क्या है?neverstringunknownstring | undefined