परिचय: टाइप्स में छिपा जादू
\n\nकल्पना करें कि आप सिर्फ कोड नहीं लिख रहे हैं, बल्कि कंपाइलर के लिए निर्देशों का एक सेट लिख रहे हैं, जो आपके नियमों के आधार पर सुरक्षित और स्व-दस्तावेजी संरचनाएं उत्पन्न करता है। यही है TypeScript में मेटाप्रोग्रामिंग — टाइप स्तर पर काम करने वाला कोड जो कंपाइलेशन के दौरान अन्य टाइप्स का विश्लेषण और रूपांतरण करता है।
\n\nकई डेवलपर्स बुनियादी generics और interfaces पर ही रुक जाते हैं, उन्हें पता भी नहीं होता कि infer, extends और conditional constructs जैसे कीवर्ड्स के पीछे कितना शक्तिशाली शस्त्रागार छिपा है। इस लेख में हम TypeScript मेटाप्रोग्रामिंग की उन्नत तकनीकों को समझेंगे, जो आपको भाषा के एक सामान्य उपयोगकर्ता से उसके वास्तुकार में बदल देंगी।
1. कंडीशनल टाइप्स: टाइप स्तर पर if
\n\nकंडीशनल टाइप्स किसी भी type-level प्रोग्रामिंग की नींव हैं। ये टर्नरी ऑपरेटर की तरह काम करते हैं, लेकिन टाइप सिस्टम के स्तर पर:
\n\ntype IsString<T> = T extends string ? "yes" : "no";\n\ntype Test1 = IsString<"hello">; // "yes"\ntype Test2 = IsString<42>; // "no"\n\n\nलेकिन असली जादू तब शुरू होता है जब हम इन्हें generics के साथ जोड़ते हैं। उदाहरण के लिए, एक ऐसा टाइप बनाएं जो किसी ऑब्जेक्ट के केवल स्ट्रिंग कीज़ को निकाले:
\n\ntype StringKeys<T> = {\n [K in keyof T]: T[K] extends string ? K : never\n}[keyof T];\n\ninterface User {\n name: string;\n age: number;\n email: string;\n}\n\ntype UserStringKeys = StringKeys<User>; // "name" | "email"\n\n\nयह उदाहरण दिखाता है कि कैसे TypeScript के उन्नत टाइप्स कंपाइलेशन के समय डेटा संरचनाओं को फ़िल्टर और रूपांतरित करने की अनुमति देते हैं। आप सचमुच ऐसी लॉजिक प्रोग्राम कर रहे हैं जो कोड के रनटाइम में जाने से पहले ही निष्पादित हो जाती है।
\n\n2. Infer: दूसरे टाइप्स से टाइप निकालना
\n\nकीवर्ड infer शायद TypeScript मेटाप्रोग्रामिंग के शस्त्रागार में सबसे शक्तिशाली उपकरण है। यह आपको टाइप के "अंदर झांकने" और उसके घटक भागों को निकालने की अनुमति देता है। आइए एक क्लासिक उदाहरण देखें — किसी फंक्शन के रिटर्न वैल्यू का टाइप प्राप्त करना:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;\n\ntype Fn = (x: number) => string;\ntype FnReturn = ReturnType<Fn>; // string\n\n\nलेकिन infer और भी बहुत कुछ कर सकता है। आइए किसी फंक्शन के पहले आर्गुमेंट का टाइप निकालें:
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;\n\ntype Handler = (id: number, name: string) => void;\ntype HandlerFirstArg = FirstArg<Handler>; // number\n\n\nऔर प्रॉमिसेज के रिकर्सिव पार्सिंग के बारे में क्या ख्याल है? एक ऐसा टाइप बनाएं जो सभी नेस्टेड Promise को "अनरैप" करता है:
\n\ntype UnwrapPromise<T> = T extends Promise<infer U> ? UnwrapPromise<U> : T;\n\ntype DeepPromise = Promise<Promise<Promise<number>>>;\ntype Unwrapped = UnwrapPromise<DeepPromise>; // number\n\n\nरिकर्शन पर ध्यान दें — यह सबसे सुंदर TypeScript की उन्नत तकनीकों में से एक है, जो अनंत रूप से नेस्टेड संरचनाओं को संभालने की अनुमति देती है।
\n\n3. मैप्ड टाइप्स: ऑब्जेक्ट्स का रूपांतरण
\n\nMapped types आपको मौजूदा टाइप्स के आधार पर नए टाइप्स बनाने की अनुमति देते हैं, उनकी कीज़ को पुनरावृत्त करके। यह सामान्यीकृत उपयोगिताएं बनाने के लिए एक अपरिहार्य उपकरण है:
\n\ntype Readonly<T> = {\n readonly [K in keyof T]: T[K];\n};\n\ntype Partial<T> = {\n [K in keyof T]?: T[K];\n};\n\n\nलेकिन हम और आगे जा सकते हैं। एक ऐसा टाइप बनाएं जो सभी प्रॉपर्टीज़ को वैकल्पिक बनाता है, लेकिन केवल तभी जब वे स्ट्रिंग हों:
\n\ntype PartialStringProps<T> = {\n [K in keyof T]: T[K] extends string ? T[K] | undefined : T[K];\n};\n\ninterface Config {\n nam