DanLevy.net

टाइपस्क्रिप्ट में गुरिल्ला टाइप्स

विद्रोही टाइप डिज़ाइन

Hero image for टाइपस्क्रिप्ट में गुरिल्ला टाइप्स

टाइपस्क्रिप्ट में गुरिल्ला टाइप्स

इस लेख में, हम टाइप डिज़ाइन में सहायता करने वाली तीन दिलचस्प (संभवतः भयानक?) तकनीकों का अन्वेषण करेंगे!

मुख्य लक्ष्य सुसंगत और अनुमानित मॉडल/एंटिटी/क्लास इंटरफ़ेस हैं।

टाइप डिज़ाइन करने के दृष्टिकोण

आपने शायद “टाइप इम्प्लीमेंटेशन” के आसपास विभिन्न पैटर्न देखे या लिखे होंगे। खासकर जब 3rd पार्टी API से डेटा का उपभोग करते हैं।

नोट: मैं जानबूझकर एंटिटी रिलेशनशिप डायग्राम (ERD) या ऑब्जेक्ट ओरिएंटेड प्रोग्रामिंग (OOP) इनहेरिटेंस हायरार्की बनाने की पारंपरिक प्रक्रियाओं को अनदेखा कर रहा हूँ। यहाँ, हम अर्ध-संरचित API डेटा का प्रतिनिधित्व करने वाले टाइप्स बना रहे हैं।

आइए दो उच्च-स्तरीय दृष्टिकोणों का अन्वेषण करें: एकल बड़ी ऑब्जेक्ट (टॉप-डाउन) बनाम कई नामित टाइप्स (बॉटम-अप।)

एकल बड़ी ऑब्जेक्ट

पुन: उपयोग योग्यता और DRY-नेस पर स्पष्ट होने को प्राथमिकता देता है।

बोनस: IDE/डेवलपर अनुभव शानदार है, क्योंकि टूलटिप में बिना किसी fuss के अधिक पूर्ण पूर्वावलोकन शामिल होता है।

interface ProductDetails {
name: string;
seller: { name: string };
availability: Array<{ warehouseId: string; quantity: number }>;
reviews: Array<{ authorId: number; stars: number }>;
}

चूँकि हम स्पष्ट पठनीयता को प्राथमिकता दे रहे हैं, कुछ दोहराव में लिप्त होना ठीक है (उचित सीमा के भीतर।) जब गुणों के समूह कई बार दोहराते हैं, तो दोहराए गए फ़ील्ड्स को एक नामित टाइप में निकालने में संकोच न करें।

कई नामित टाइप्स

पुन: उपयोग योग्यता और DRY-नेस को प्राथमिकता देना।

यह दृष्टिकोण संभवतः व्यापक रूप से पसंदीदा दृष्टिकोण है।

interface ProductDetails {
name: string;
seller: Seller;
reviews: Reviews[];
availability: Availability[];
}
interface Seller { name: string; }
interface Availability { warehouseId: string; quantity: number; }
interface Reviews { authorId: number; stars: number; }

समग्र रूप से, यह दृष्टिकोण शानदार है। लेकिन यह drawbacks के बिना नहीं है।

⚠️ चूँकि (लगभग) टाइपस्क्रिप्ट v3 से, लैंग्वेज सर्वर टूलटिप को ट्रंकेट करता है, नेस्टेड प्रॉपर्टीज़ को छोड़ देता है। 💡 चीज़ों को थोड़ा बेहतर बनाने के ट्रिक्स हैं। Cmd या Ctrl को होल्ड करने का प्रयास करें, फिर विभिन्न टाइप नामों पर होवर करें - आपको टूलटिप में कम से कम एक अतिरिक्त ‘लेयर’ प्रॉपर्टीज़ दिखनी चाहिए।

हमें इन दो दृष्टिकोणों के बीच क्यों चुनना है? (बड़ा टाइप बनाम नामित सब-टाइप्स।)

तकनीक #1: क्यों नहीं सभी

क्या हमारे पास सब कुछ हो सकता है?

✅ हाँ! 🎉

export interface ProductDetails {
name: string;
seller: { name: string };
reviews: Array<{ authorId: number; stars: number }>;
availability: Array<{ warehouseId: string; quantity: number }>;
}
export type Seller = ProductDetails["seller"];
export type Review = ProductDetails["reviews"][number];
export type Availability = ProductDetails["availability"][number];
  1. बड़े “प्राथमिक” संरचित टाइप्स बनाएँ।
  2. प्राथमिक टाइप से प्राप्त सब-टाइप्स एक्सपोर्ट करें।

यह दृष्टिकोण उन सिस्टमों में वास्तव में चमकता है जहाँ “उच्च-स्तरीय” ऑब्जेक्ट्स एक जगह दस्तावेज़ीकरण से लाभान्वित होते हैं। इसके अलावा, यह तकनीक मॉडल, सर्विसेज़, क्वेरी रिज़ल्ट्स आदि जैसे कई उपयोग मामलों के बीच पुन: उपयोग का समर्थन करती है।

तकनीक #2: मिक्स-इन

यह रणनीति सही फ़ील्ड्स को, सही नामों के साथ, एकल तार्किक ऑब्जेक्ट्स का प्रतिनिधित्व करने के बारे में है। लक्ष्य टाइपस्क्रिप्ट यूटिलिटीज़ और टाइप यूनियन्स के साथ कई उपयोग मामलों को कुशलता से संबोधित करना है।

यह दृष्टिकोण पारंपरिक OOP इनहेरिटेंस और हायरार्की से भिन्न है, जिसका लक्ष्य ऑब्जेक्ट्स की परतों को tightly bound taxonomies में बनाना है। मिक्स-इन दृष्टिकोण flat और loosely-related types के बारे में है, संबंधित फ़ील्ड्स को समूहित करते हुए दोहराव को कम करता है।

मिक्स-इन उदाहरण

interface TodoModel {
text: string;
complete: boolean;
}
interface InstanceMixin {
id: number;
}
/** TodoDraft represents Form state, possibly all undefined */
export type TodoDraft = Partial<TodoModel>;
/** Todo represents a Todo instance record from the database */
export type Todo = TodoModel & InstanceMixin;

उदाहरण User

interface User {
id: number;
name: string;
bio: string;
social: Record<"facebook" | "instagram" | "github", URL>;
}

आइए डेटाबेस में सेव करने से पहले और बाद में User का प्रतिनिधित्व करें।

// Core User fields (say for a <form>)
interface UserBase {
name: string;
bio: string;
social: Record<"facebook" | "instagram" | "github", URL>;
}
// Fields from the database
interface InstanceMixin {
id: number;
createdAt: Date;
updatedAt: Date;
}
// A User **instance** - with all fields
type UserInstance = InstanceMixin & UserBase;

अब हमें ज़रूरत के बिल्कुल सही फ़ील्ड्स को sculpt कर सकते हैं (जैसे create/update के लिए password, लेकिन UserInstance की queries में शामिल नहीं)।

interface UserBase {
name: string;
bio: string;
social: Record<"facebook" | "instagram" | "github", URL>;
}
interface InstanceMixin {
id: number;
createdAt: Date;
updatedAt: Date;
}
/** User payload for signup, including `password` field */
export type UserPayload = UserBase & { password: string };
/** Represents User type returned from server. */
export type UserInstance = UserBase & InstanceMixin;
  1. “क्या यह एक अच्छा अभ्यास है?”
  2. “क्या मुझे इसे आज़माना चाहिए?”

कोई विचार नहीं। चलिए चलते रहें!

तकनीक #3: नेमस्पेस के साथ व्यवस्थित करना

यहाँ, हम एक ModelMixins नेमस्पेस घोषित करते हैं। यह कुछ संगठन और एक स्पष्ट पुन: उपयोग पैटर्न प्रदान करता है।

मानकीकृत आकृतियाँ

// `src/types/mixins.d.ts`
namespace ModelMixins {
interface Identity {
id: number;
}
interface Timestamp {
createdAt: Date;
updatedAt: Date;
}
type Instance = ModelMixins.Identity & ModelMixins.Timestamp;
interface HashedPassword {
passwordHash: string;
}
interface InputPassword {
password: string;
}
}

टाइप यूनियन्स का उपयोग करना

// `src/types/user.d.ts`
export interface UserBase {
name: string;
bio: string;
social: Record<"facebook" | "instagram" | "github", URL>;
}
// Single `User` type, using Type Union to dynamically
// represent the pre- & post-creation states.
export type User =
| (UserBase & ModelMixins.Instance & ModelMixins.HashedPassword)
| (UserBase & ModelMixins.InputPassword);

यदि वांछित हो, तो आप व्यक्तिगत नामित टाइप्स भी एक्सपोर्ट कर सकते हैं:

/** User payload for signup, including `password` field */
export type UserPayload = UserBase & ModelMixins.Instance & ModelMixins.HashedPassword;
/** Represents User type returned from server. */
export type UserInstance = UserBase & ModelMixins.InputPassword;

वास्तविक-दुनिया उपयोग

यहाँ एक upsert() फ़ंक्शन है जो UserInstance और UserPayload टाइप्स के बीच अंतर करने के लिए in ऑपरेटर का उपयोग करता है।

function upsert(user: User) {
if ("id" in user) {
// TypeScript knows `user` here has fields from Instance (id, createdAt, etc)
return updateUser(user.id, user);
} else {
// TypeScript knows this must be the `UserBase & ModelMixins.InputPassword` version of user.
return createUser(user);
}
}

सारांश

हमने तीन तकनीकों और कुछ संबंधित सहायक विचारों को कवर किया।

आप पूछ रहे होंगे, क्या ये अच्छे पैटर्न हैं? क्या मुझे इन विचारों को अपनाना चाहिए?

संसाधन