Kodokon kodokon.com

कंडीशनल टाइप्स और infer: कंपाइलर का तर्क

कंडीशनल टाइप्स की वितरणशीलता, infer के साथ पैटर्न मैचिंग, और never, any तथा boolean जैसे किनारे के मामलों में महारत हासिल करें।

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

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

कंडीशनल टाइप्स टाइप सिस्टम का ब्रांचिंग तंत्र हैं: T extends U ? X : Y कंपाइलर से पूछता है कि क्या T को U को असाइन किया जा सकता है, फिर एक शाखा चुनता है। ReturnType, Exclude, या Awaited जैसी सभी मानक यूटिलिटीज़ इन्हीं पर बनी हैं। इस स्तर पर सवाल यह नहीं है कि इन्हें कैसे लिखें, बल्कि यह है कि ठीक-ठीक कैसे अनुमान लगाएं कि कंपाइलर इनका मूल्यांकन कैसे करता है, खासकर जब यूनियन और never शामिल हों।

TYPESCRIPT
type IsString<T> = T extends string ? true : false;

type A = IsString<"hello">;
// true

type B = IsString<string | number>;
// boolean, not false!
एक यूनियन पर चौंकाने वाला परिणाम: boolean।

मुख्य व्यवहार है वितरणशीलता (distributivity): जब परखा जा रहा टाइप एक नंगा टाइप पैरामीटर (naked type parameter) होता है (जो किसी ट्यूपल, ऐरे, या इंटरसेक्शन में लिपटा न हो), तो कंडीशनल यूनियन के प्रत्येक सदस्य पर अलग-अलग लागू होता है, और फिर परिणामों को जोड़ दिया जाता है। इसीलिए IsString<string | number> की गणना true | false होती है, जो कि boolean है। इस वितरण को अक्षम करने के लिए, दोनों पक्षों को एक ट्यूपल में लपेटें: [T] extends [U]। एक भयानक किनारे का मामला: never खाली यूनियन है; शून्य सदस्यों पर वितरण किसी भी शाखा का मूल्यांकन नहीं करता, और आपने चाहे जो शाखा लिखी हो, परिणाम never ही रहता है।

TYPESCRIPT
type ToArray<T> = T extends unknown ? T[] : never;
type NoDist<T> = [T] extends [unknown] ? T[] : never;

type A = ToArray<string | number>;
// string[] | number[]

type B = NoDist<string | number>;
// (string | number)[]

type IsNever<T> = [T] extends [never] ? true : false;
type C = IsNever<never>; // true thanks to the tuple
ट्यूपल वितरण को निष्प्रभावी करता है और never को भरोसेमंद बनाता है।

infer कीवर्ड एक ऐसा टाइप वेरिएबल प्रस्तुत करता है जो पैटर्न मैचिंग के माध्यम से पकड़ा जाता है: कंपाइलर परखे जा रहे टाइप की संरचना को पैटर्न से मिलाने की कोशिश करता है, और मैच सफल होने पर वेरिएबल को बांध देता है। आप एक ही नाम के साथ कई infer घोषणाएं रख सकते हैं: एक सहचर स्थिति (covariant position) में (उत्पादित मान, जैसे रिटर्न टाइप), उम्मीदवारों को एक यूनियन में मिला दिया जाता है; एक प्रतिचर स्थिति (contravariant position) में (फ़ंक्शन पैरामीटर), उन्हें एक इंटरसेक्शन में जोड़ा जाता है। यह नियम सीधे वेरिएंस से निकलता है: प्रत्येक दिशा में यही एकमात्र सुरक्षित विकल्प है।

TYPESCRIPT
type ElementOf<T> =
  T extends readonly (infer E)[] ? E : never;

type FirstParam<T> =
  T extends (arg: infer P) => unknown ? P : never;

type Merged<T> =
  T extends { a: (x: infer P) => void;
              b: (x: infer P) => void }
    ? P : never;

type M = Merged<{
  a: (x: { id: string }) => void;
  b: (x: { tag: string }) => void;
}>; // { id: string } & { tag: string }
प्रतिचर स्थिति में दो infer: इंटरसेक्शन।

TypeScript 4.7 से, infer एक इनलाइन कंस्ट्रेंट स्वीकार करता है: infer N extends number कैप्चर को छानता है, और 4.8 से कंपाइलर एक संख्यात्मक स्ट्रिंग लिटरल को संख्या लिटरल में भी बदल देता है। एक और विनिर्देश की बारीकी: एक कंडीशनल टाइप जिसके इनपुट में अभी भी कोई अनसुलझा जेनेरिक पैरामीटर हो, वह स्थगित (deferred) रहता है; इसका मूल्यांकन केवल इंस्टैंसिएशन स्थल पर होता है, जिससे यह समझ आता है कि कुछ त्रुटियां केवल कॉलर के पक्ष में क्यों दिखती हैं।

TYPESCRIPT
type ToNumber<S extends string> =
  S extends `${infer N extends number}` ? N : never;

type Port = ToNumber<"8080">; // the literal 8080
type Bad = ToNumber<"abc">;   // never
infer पर कंस्ट्रेंट और लिटरल रूपांतरण (4.8)।

ज्ञान जांच

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

  1. टाइप Exclude<boolean, true> क्या है?
    • boolean
    • false
    • never
    • true
  2. type IsNever<T> = T extends never ? true : false; के साथ, IsNever<never> क्या है?
    • true
    • false
    • never
    • boolean
  3. एक ही नाम वाले दो infer P जो फ़ंक्शन पैरामीटर स्थिति में रखे गए हों, वे उत्पन्न करते हैं:
    • उम्मीदवारों का एक यूनियन
    • उम्मीदवारों का एक इंटरसेक्शन
    • पहला मिलने वाला उम्मीदवार
    • एक कंपाइलेशन त्रुटि