AD

TypeScript में Generics का इस्तेमाल करके Reusable और Type-Safe कोड कैसे लिखें: प्रैक्टिकल गाइड

TypeScript में Generics का इस्तेमाल करके Reusable और Type-Safe कोड कैसे लिखें: प्रैक्टिकल गाइड

मॉडर्न वेब डेवलपमेंट में TypeScript सबसे लोकप्रिय भाषाओं में से एक बन चुकी है। इसका सबसे बड़ा कारण है इसका स्ट्रॉन्ग टाइप सिस्टम, जो कोड रन होने से पहले ही बग्स और गलतियों को पकड़ लेता है। लेकिन अक्सर डेवलपर्स एक आम समस्या में फंसते हैं—जब उन्हें अलग-अलग डेटा टाइप्स (जैसे number, string या custom objects) के लिए एक जैसा लॉजिक लिखना होता है, तो वे या तो बार-बार डुप्लीकेट कोड लिखते हैं या फिर any टाइप का सहारा लेते हैं।

कोड में any का इस्तेमाल करने से TypeScript की पूरी टाइप-सेफ्टी खत्म हो जाती है। इसी समस्या का सबसे शक्तिशाली समाधान है Generics। इस प्रैक्टिकल गाइड में हम स्टेप-बाय-स्टेप सीखेंगे कि TypeScript Generics का इस्तेमाल करके फ्लेक्सिबल, रीयूजेबल और पूरी तरह टाइप-सेफ कोड कैसे लिखा जाता है।

स्टेप 1: बिना Generics के आने वाली समस्या को समझें

मान लीजिए आपको एक ऐसा हेल्पर फंक्शन बनाना है जो किसी भी ऐरे (Array) का पहला एलिमेंट रिटर्न करे। यदि हम इसे बिना Generics के बनाएं, तो हमारे पास दो रास्ते होते हैं:

तरीका A: अलग-अलग डेटा टाइप के लिए अलग फंक्शन बनाना

function getFirstNumber(arr: number[]): number {
  return arr[0];
}

function getFirstString(arr: string[]): string {
  return arr[0];
}

इस तरीके में एक ही लॉजिक को बार-बार दोहराना पड़ रहा है, जो DRY (Don't Repeat Yourself) सिद्धांत के खिलाफ है।

तरीका B: any टाइप का उपयोग करना

function getFirstElement(arr: any[]): any {
  return arr[0];
}

const item = getFirstElement(["Rahul", "Amit", "Pooja"]);
// यहाँ TypeScript को पता नहीं है कि item एक string है!

यहाँ any लगाने से कोड तो काम करेगा, लेकिन हम ऑटो-कंप्लीशन (IntelliSense) और टाइप-चेकिंग खो देते हैं। यदि आप गलती से item.toFixed() लिख देंगे, तो कोड कंपाइल तो हो जाएगा लेकिन ब्राउज़र में रनटाइम पर क्रैश कर जाएगा।

स्टेप 2: अपना पहला Generic Function बनाएं

Generics हमें एक ऐसा 'टाइप वेरिएबल' बनाने की सुविधा देते हैं जो फंक्शन कॉल करते समय तय होता है। इसे आमतौर पर <T> (Type का संक्षिप्त रूप) से दर्शाया जाता है।

function getFirstItem<T>(items: T[]): T {
  return items[0];
}

// स्ट्रिंग ऐरे के साथ इस्तेमाल
const nameList = ["Vikram", "Neha", "Suresh"];
const firstName = getFirstItem(nameList); // TypeScript अपने आप समझ जाता है कि firstName: string है

// नंबर ऐरे के साथ इस्तेमाल
const scoreList = [95, 82, 74];
const topScore = getFirstItem(scoreList); // topScore: number है

यहाँ <T> एक प्लेसहोल्डर की तरह काम करता है। जब आपने nameList पास की, तो T की जगह string आ गया। इससे कोड पूरी तरह रीयूजेबल भी रहा और सख्त टाइप-सेफ्टी भी बनी रही।

स्टेप 3: Generic Interfaces और Models तैयार करें

रियल-वर्ल्ड ऍप्लिकेशन्स में हम अक्सर बैकएंड API से डेटा फेच करते हैं। सभी API रिस्पॉन्स का स्ट्रक्चर आमतौर पर एक जैसा होता है (status, message, data), लेकिन उनके data का टाइप हमेशा बदलता रहता है। इसके लिए Generic Interface सबसे सही समाधान है।

// जेनेरिक API रिस्पॉन्स इंटरफेस
interface ApiResponse<T> {
  success: boolean;
  status: number;
  data: T;
  message?: string;
}

// अलग-अलग डेटा मॉडल्स
interface UserProfile {
  id: number;
  name: string;
  email: string;
}

interface ProductItem {
  sku: string;
  price: number;
}

// User API के लिए उपयोग
const userResponse: ApiResponse<UserProfile> = {
  success: true,
  status: 200,
  data: {
    id: 101,
    name: "Aman Sharma",
    email: "[email protected]"
  }
};

// Product API के लिए वही इंटरफेस रीयूज करें
const productResponse: ApiResponse<ProductItem> = {
  success: true,
  status: 200,
  data: {
    sku: "PROD-9981",
    price: 1499
  }
};

इस पैटर्न से आपको हर नई API के लिए अलग रिस्पॉन्स रैपर नहीं बनाना पड़ता। एक ही इंटरफेस हर डेटा मॉडल के साथ परफेक्ट टाइप-सेफ्टी देता है।

स्टेप 4: Generic Constraints (extends) से टाइप पर पाबंदी लगाएं

कभी-कभी आप चाहते हैं कि आपका जेनेरिक फंक्शन किसी भी टाइप को स्वीकार न करे, बल्कि केवल उन्हीं ऑब्जेक्ट्स को स्वीकार करे जिनमें कुछ खास प्रॉपर्टीज मौजूद हों। इसके लिए extends कीवर्ड का उपयोग किया जाता है।

उदाहरण के लिए, मान लीजिए हमें एक ऐसा फंक्शन बनाना है जो किसी भी ऑब्जेक्ट की id प्रिंट करे:

interface Identifiable {
  id: number | string;
}

// T को Identifiable को extend करना ही होगा
function printEntityId<T extends Identifiable>(entity: T): void {
  console.log(`Entity ID: ${entity.id}`);
}

// सही इस्तेमाल:
printEntityId({ id: 501, title: "TypeScript Guide" }); // वैध

// गलत इस्तेमाल (कंपाइलर एरर देगा):
// printEntityId({ title: "No ID Here" }); 
// Error: Property 'id' is missing!

Generics लिखते समय डेवलपर्स की 3 सबसे आम गलतियां

1. ज़रूरत न होने पर भी जबरन Generics का उपयोग करना

यदि आपका फंक्शन सिर्फ एक ही फिक्स्ड टाइप (जैसे सिर्फ string) के साथ काम करने वाला है, तो वहाँ <T> लगाने से कोड सिर्फ जटिल बनता है, बेहतर नहीं। जेनेरिक्स का इस्तेमाल तभी करें जब कोड को वास्तव में मल्टीपल टाइप्स के साथ रीयूज करना हो।

2. Constraints भूल जाना और प्रॉपर्टी एरर पाना

यदि आप किसी Generic टाइप T पर value.length कॉल करते हैं बिना यह बताए कि T में length मौजूद है, तो TypeScript एरर देगा (Property 'length' does not exist on type 'T')। हमेशा extends { length: number } का उपयोग करें।

3. जेनेरिक्स के अंदर any को कास्ट करना

जेनेरिक फंक्शन के अंदर return item as any; लिखना एक एंटी-पैटर्न है। ऐसा करने से जेनेरिक्स का मूल उद्देश्य ही खत्म हो जाता है। कंपाइलर के टाइप इंफरेंस पर भरोसा करें।

क्लीन कोड के लिए बेस्ट प्रैक्टिसेज

  • स्पष्ट नामकरण: छोटे कोड में T, U, K, V मानक हैं, लेकिन बड़े और जटिल प्रोजेक्ट्स में डिस्क्रिप्टिव नाम जैसे TData, TError, या TResponse का उपयोग करें।
  • Default Generics: आप जेनेरिक्स में डिफॉल्ट टाइप भी दे सकते हैं, जैसे interface ApiResponse<T = unknown>। इससे यूजर के लिए टाइप पास करना ऑप्शनल हो जाता है।
  • Keyof ऑपरेटर का कॉम्बिनेशन: सुरक्षित ऑब्जेक्ट प्रॉपर्टी एक्सेस के लिए <T, K extends keyof T> का कॉम्बो इस्तेमाल करें।

अक्सर पूछे जाने वाले सवाल (FAQ)

1. क्या Generics के इस्तेमाल से JavaScript का बंडल साइज बढ़ता है?

बिल्कुल नहीं। TypeScript कंपाइलेशन के दौरान सभी टाइप्स और जेनेरिक्स को पूरी तरह हटा (strip) दिया जाता है। आपके फाइनल JavaScript आउटपुट में कोई एक्स्ट्रा कोड या ओवरहेड नहीं जुड़ता।

2. any और unknown की तुलना में Generics बेहतर क्यों है?

any टाइप-चेकिंग बंद कर देता है, और unknown में इस्तेमाल से पहले बार-बार टाइप गार्ड्स लगाने पड़ते हैं। Generics इनपुट और आउटपुट के बीच के वास्तविक संबंध को बनाए रखते हैं बिना टाइप-सेफ्टी खोए।

3. क्या हम एक फंक्शन में एक से अधिक Generics ले सकते हैं?

हाँ, आप कॉमा लगाकर जितने चाहें उतने जेनेरिक पैरामीटर्स ले सकते हैं, जैसे function merge<T, U>(obj1: T, obj2: U)।

4. TypeScript Generics कब नहीं इस्तेमाल करना चाहिए?

अगर दो या तीन निश्चित टाइप्स ही संभव हैं, तो Union Types (जैसे string | number) का इस्तेमाल करना Generics से ज्यादा आसान और पढ़ने में साफ होता है।

Post a Comment

0 Comments