فارسی

راهنمای جامع انواع نگاشتی (Mapped Types) و انواع شرطی (Conditional Types) قدرتمند تایپ‌اسکریپت، شامل مثال‌های عملی و موارد استفاده پیشرفته برای ساخت برنامه‌های قوی و امن از نظر نوع.

تسلط بر انواع نگاشتی و انواع شرطی در تایپ‌اسکریپت

تایپ‌اسکریپت، که یک بالامجموعه (superset) از جاوااسکریپت است، ویژگی‌های قدرتمندی برای ساخت برنامه‌های قوی و قابل نگهداری ارائه می‌دهد. در میان این ویژگی‌ها، انواع نگاشتی (Mapped Types) و انواع شرطی (Conditional Types) به عنوان ابزارهای ضروری برای دستکاری پیشرفته انواع داده برجسته هستند. این راهنما یک نمای کلی از این مفاهیم ارائه می‌دهد و سینتکس، کاربردهای عملی و موارد استفاده پیشرفته آن‌ها را بررسی می‌کند. چه یک توسعه‌دهنده باتجربه تایپ‌اسکریپت باشید و چه در ابتدای راه، این مقاله شما را به دانشی مجهز می‌کند تا از این ویژگی‌ها به طور مؤثر استفاده کنید.

انواع نگاشتی (Mapped Types) چه هستند؟

انواع نگاشتی به شما این امکان را می‌دهند که با تبدیل انواع موجود، انواع جدیدی ایجاد کنید. آن‌ها بر روی خصوصیات (properties) یک نوع موجود پیمایش کرده و یک تبدیل را روی هر خصوصیت اعمال می‌کنند. این ویژگی به خصوص برای ایجاد نسخه‌های مختلف از انواع موجود، مانند اختیاری (optional) یا فقط-خواندنی (read-only) کردن تمام خصوصیات، بسیار مفید است.

سینتکس پایه

سینتکس یک نوع نگاشتی به شرح زیر است:

type NewType<T> = {
  [K in keyof T]: Transformation;
};

مثال‌های عملی

فقط-خواندنی کردن خصوصیات

فرض کنید یک اینترفیس دارید که پروفایل کاربر را نشان می‌دهد:

interface UserProfile {
  name: string;
  age: number;
  email: string;
}

شما می‌توانید یک نوع جدید ایجاد کنید که در آن تمام خصوصیات فقط-خواندنی باشند:

type ReadOnlyUserProfile = {
  readonly [K in keyof UserProfile]: UserProfile[K];
};

اکنون، ReadOnlyUserProfile همان خصوصیات UserProfile را خواهد داشت، اما همه آن‌ها فقط-خواندنی خواهند بود.

اختیاری کردن خصوصیات

به طور مشابه، می‌توانید تمام خصوصیات را اختیاری کنید:

type OptionalUserProfile = {
  [K in keyof UserProfile]?: UserProfile[K];
};

OptionalUserProfile تمام خصوصیات UserProfile را خواهد داشت، اما هر خصوصیت اختیاری خواهد بود.

تغییر نوع خصوصیات

شما همچنین می‌توانید نوع هر خصوصیت را تغییر دهید. برای مثال، می‌توانید تمام خصوصیات را به رشته (string) تبدیل کنید:

type StringifiedUserProfile = {
  [K in keyof UserProfile]: string;
};

در این حالت، تمام خصوصیات در StringifiedUserProfile از نوع string خواهند بود.

انواع شرطی (Conditional Types) چه هستند؟

انواع شرطی به شما این امکان را می‌دهند که انواعی را تعریف کنید که به یک شرط بستگی دارند. آن‌ها راهی برای بیان روابط نوع بر اساس اینکه آیا یک نوع، محدودیت خاصی را برآورده می‌کند یا نه، فراهم می‌کنند. این شبیه به عملگر سه‌تایی (ternary operator) در جاوااسکریپت است، اما برای انواع.

سینتکس پایه

سینتکس یک نوع شرطی به شرح زیر است:

T extends U ? X : Y

مثال‌های عملی

تشخیص اینکه آیا یک نوع رشته است

بیایید یک نوع ایجاد کنیم که اگر نوع ورودی رشته باشد، string را برگرداند و در غیر این صورت number را:

type StringOrNumber<T> = T extends string ? string : number;

type Result1 = StringOrNumber<string>;  // string
type Result2 = StringOrNumber<number>;  // number
type Result3 = StringOrNumber<boolean>; // number

استخراج نوع از یک اجتماع (Union)

شما می‌توانید از انواع شرطی برای استخراج یک نوع خاص از یک نوع اجتماع استفاده کنید. برای مثال، برای استخراج انواع غیر-تهی (non-nullable):

type NonNullable<T> = T extends null | undefined ? never : T;

type Result4 = NonNullable<string | null | undefined>; // string

در اینجا، اگر T برابر با null یا undefined باشد، نوع به never تبدیل می‌شود که سپس توسط ساده‌سازی نوع اجتماع تایپ‌اسکریپت فیلتر می‌شود.

استنتاج انواع (Inferring Types)

انواع شرطی همچنین می‌توانند برای استنتاج انواع با استفاده از کلمه کلیدی infer استفاده شوند. این به شما امکان می‌دهد تا یک نوع را از یک ساختار نوع پیچیده‌تر استخراج کنید.

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

function myFunction(x: number): string {
  return x.toString();
}

type Result5 = ReturnType<typeof myFunction>; // string

در این مثال، ReturnType نوع بازگشتی یک تابع را استخراج می‌کند. این بررسی می‌کند که آیا T یک تابع است که هر آرگومانی را می‌گیرد و یک نوع R را برمی‌گرداند. اگر چنین باشد، R را برمی‌گرداند؛ در غیر این صورت، any را برمی‌گرداند.

ترکیب انواع نگاشتی و انواع شرطی

قدرت واقعی انواع نگاشتی و انواع شرطی از ترکیب آن‌ها ناشی می‌شود. این به شما امکان می‌دهد تا تبدیلات نوع بسیار انعطاف‌پذیر و گویایی ایجاد کنید.

مثال: Deep Readonly (فقط-خواندنی عمیق)

یک مورد استفاده رایج، ایجاد یک نوع است که تمام خصوصیات یک شیء، از جمله خصوصیات تودرتو را، فقط-خواندنی می‌کند. این کار را می‌توان با استفاده از یک نوع شرطی بازگشتی انجام داد.

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

interface Company {
  name: string;
  address: {
    street: string;
    city: string;
  };
}

type ReadonlyCompany = DeepReadonly<Company>;

در اینجا، DeepReadonly به صورت بازگشتی اصلاح‌کننده readonly را به تمام خصوصیات و خصوصیات تودرتو آن‌ها اعمال می‌کند. اگر یک خصوصیت یک شیء باشد، به صورت بازگشتی DeepReadonly را روی آن شیء فراخوانی می‌کند. در غیر این صورت، به سادگی اصلاح‌کننده readonly را به آن خصوصیت اعمال می‌کند.

مثال: فیلتر کردن خصوصیات بر اساس نوع

فرض کنید می‌خواهید یک نوع ایجاد کنید که فقط شامل خصوصیات یک نوع خاص باشد. شما می‌توانید انواع نگاشتی و انواع شرطی را برای دستیابی به این هدف ترکیب کنید.

type FilterByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K];
};

interface Person {
  name: string;
  age: number;
  isEmployed: boolean;
}

type StringProperties = FilterByType<Person, string>; // { name: string; }

type NonStringProperties = Omit<Person, keyof StringProperties>;

در این مثال، FilterByType بر روی خصوصیات T پیمایش می‌کند و بررسی می‌کند که آیا نوع هر خصوصیت از U مشتق شده است یا خیر. اگر چنین باشد، خصوصیت را در نوع حاصل شامل می‌کند؛ در غیر این صورت، با نگاشت کلید به never، آن را حذف می‌کند. به استفاده از "as" برای نگاشت مجدد کلیدها توجه کنید. سپس ما از `Omit` و `keyof StringProperties` برای حذف خصوصیات رشته‌ای از اینترفیس اصلی استفاده می‌کنیم.

موارد استفاده و الگوهای پیشرفته

فراتر از مثال‌های پایه، انواع نگاشتی و انواع شرطی می‌توانند در سناریوهای پیشرفته‌تری برای ایجاد برنامه‌های بسیار قابل تنظیم و امن از نظر نوع استفاده شوند.

انواع شرطی توزیعی (Distributive Conditional Types)

انواع شرطی زمانی توزیعی هستند که نوع مورد بررسی یک نوع اجتماع (union type) باشد. این بدان معناست که شرط به طور جداگانه برای هر عضو اجتماع اعمال می‌شود و نتایج سپس در یک نوع اجتماع جدید ترکیب می‌شوند.

type ToArray<T> = T extends any ? T[] : never;

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

در این مثال، ToArray به طور جداگانه برای هر عضو از اجتماع string | number اعمال می‌شود که منجر به string[] | number[] می‌شود. اگر شرط توزیعی نبود، نتیجه (string | number)[] می‌شد.

استفاده از انواع کاربردی (Utility Types)

تایپ‌اسکریپت چندین نوع کاربردی داخلی ارائه می‌دهد که از انواع نگاشتی و انواع شرطی بهره می‌برند. این انواع کاربردی می‌توانند به عنوان بلوک‌های سازنده برای تبدیلات نوع پیچیده‌تر استفاده شوند.

این انواع کاربردی ابزارهای قدرتمندی هستند که می‌توانند دستکاری‌های پیچیده نوع را ساده کنند. برای مثال، شما می‌توانید Pick و Partial را ترکیب کنید تا نوعی ایجاد کنید که فقط خصوصیات خاصی را اختیاری می‌کند:

type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

interface Product {
  id: number;
  name: string;
  price: number;
  description: string;
}

type OptionalDescriptionProduct = Optional<Product, "description">;

در این مثال، OptionalDescriptionProduct تمام خصوصیات Product را دارد، اما خصوصیت description اختیاری است.

استفاده از انواع قالب رشته‌ای (Template Literal Types)

انواع قالب رشته‌ای به شما امکان می‌دهند تا انواعی بر اساس رشته‌های ثابت ایجاد کنید. آن‌ها می‌توانند در ترکیب با انواع نگاشتی و انواع شرطی برای ایجاد تبدیلات نوع پویا و گویا استفاده شوند. برای مثال، می‌توانید نوعی ایجاد کنید که به ابتدای تمام نام‌های خصوصیات یک رشته خاص اضافه کند:

type Prefix<T, P extends string> = {
  [K in keyof T as `${P}${string & K}`]: T[K];
};

interface Settings {
  apiUrl: string;
  timeout: number;
}

type PrefixedSettings = Prefix<Settings, "data_">;

در این مثال، PrefixedSettings خصوصیات data_apiUrl و data_timeout را خواهد داشت.

بهترین شیوه‌ها و ملاحظات

نتیجه‌گیری

انواع نگاشتی و انواع شرطی ویژگی‌های قدرتمندی در تایپ‌اسکریپت هستند که به شما امکان می‌دهند تبدیلات نوع بسیار انعطاف‌پذیر و گویایی ایجاد کنید. با تسلط بر این مفاهیم، می‌توانید امنیت نوع، قابلیت نگهداری و کیفیت کلی برنامه‌های تایپ‌اسکریپت خود را بهبود بخشید. از تبدیلات ساده مانند اختیاری یا فقط-خواندنی کردن خصوصیات گرفته تا تبدیلات بازگشتی پیچیده و منطق شرطی، این ویژگی‌ها ابزارهایی را که برای ساخت برنامه‌های قوی و مقیاس‌پذیر نیاز دارید، فراهم می‌کنند. به کاوش و آزمایش با این ویژگی‌ها ادامه دهید تا پتانسیل کامل آن‌ها را آزاد کرده و به یک توسعه‌دهنده ماهرتر تایپ‌اسکریپت تبدیل شوید.

همانطور که سفر تایپ‌اسکریپت خود را ادامه می‌دهید، به یاد داشته باشید که از منابع فراوان موجود، از جمله مستندات رسمی تایپ‌اسکریپت، جوامع آنلاین و پروژه‌های منبع باز، بهره ببرید. قدرت انواع نگاشتی و انواع شرطی را در آغوش بگیرید و برای مقابله با حتی چالش‌برانگیزترین مشکلات مربوط به نوع، به خوبی مجهز خواهید شد.