راهنمای جامع انواع نگاشتی (Mapped Types) و انواع شرطی (Conditional Types) قدرتمند تایپاسکریپت، شامل مثالهای عملی و موارد استفاده پیشرفته برای ساخت برنامههای قوی و امن از نظر نوع.
تسلط بر انواع نگاشتی و انواع شرطی در تایپاسکریپت
تایپاسکریپت، که یک بالامجموعه (superset) از جاوااسکریپت است، ویژگیهای قدرتمندی برای ساخت برنامههای قوی و قابل نگهداری ارائه میدهد. در میان این ویژگیها، انواع نگاشتی (Mapped Types) و انواع شرطی (Conditional Types) به عنوان ابزارهای ضروری برای دستکاری پیشرفته انواع داده برجسته هستند. این راهنما یک نمای کلی از این مفاهیم ارائه میدهد و سینتکس، کاربردهای عملی و موارد استفاده پیشرفته آنها را بررسی میکند. چه یک توسعهدهنده باتجربه تایپاسکریپت باشید و چه در ابتدای راه، این مقاله شما را به دانشی مجهز میکند تا از این ویژگیها به طور مؤثر استفاده کنید.
انواع نگاشتی (Mapped Types) چه هستند؟
انواع نگاشتی به شما این امکان را میدهند که با تبدیل انواع موجود، انواع جدیدی ایجاد کنید. آنها بر روی خصوصیات (properties) یک نوع موجود پیمایش کرده و یک تبدیل را روی هر خصوصیت اعمال میکنند. این ویژگی به خصوص برای ایجاد نسخههای مختلف از انواع موجود، مانند اختیاری (optional) یا فقط-خواندنی (read-only) کردن تمام خصوصیات، بسیار مفید است.
سینتکس پایه
سینتکس یک نوع نگاشتی به شرح زیر است:
type NewType<T> = {
[K in keyof T]: Transformation;
};
T
: نوع ورودی که میخواهید روی آن نگاشت انجام دهید.K in keyof T
: روی هر کلید در نوع ورودیT
پیمایش میکند.keyof T
یک اجتماع (union) از تمام نامهای خصوصیات درT
ایجاد میکند وK
نماینده هر کلید در طول پیمایش است.Transformation
: تبدیلی که میخواهید روی هر خصوصیت اعمال کنید. این میتواند افزودن یک اصلاحکننده (مانندreadonly
یا?
)، تغییر نوع، یا چیز دیگری باشد.
مثالهای عملی
فقط-خواندنی کردن خصوصیات
فرض کنید یک اینترفیس دارید که پروفایل کاربر را نشان میدهد:
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
T
: نوعی که در حال بررسی است.U
: نوعی کهT
باید از آن مشتق شده باشد (شرط).X
: نوعی که اگرT
ازU
مشتق شود (شرط درست باشد) برگردانده میشود.Y
: نوعی که اگرT
ازU
مشتق نشود (شرط نادرست باشد) برگردانده میشود.
مثالهای عملی
تشخیص اینکه آیا یک نوع رشته است
بیایید یک نوع ایجاد کنیم که اگر نوع ورودی رشته باشد، 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)
تایپاسکریپت چندین نوع کاربردی داخلی ارائه میدهد که از انواع نگاشتی و انواع شرطی بهره میبرند. این انواع کاربردی میتوانند به عنوان بلوکهای سازنده برای تبدیلات نوع پیچیدهتر استفاده شوند.
Partial<T>
: تمام خصوصیاتT
را اختیاری میکند.Required<T>
: تمام خصوصیاتT
را الزامی میکند.Readonly<T>
: تمام خصوصیاتT
را فقط-خواندنی میکند.Pick<T, K>
: مجموعهای از خصوصیاتK
را ازT
انتخاب میکند.Omit<T, K>
: مجموعهای از خصوصیاتK
را ازT
حذف میکند.Record<K, T>
: نوعی با مجموعهای از خصوصیاتK
از نوعT
میسازد.Exclude<T, U>
: تمام انواعی که قابل تخصیص بهU
هستند را ازT
حذف میکند.Extract<T, U>
: تمام انواعی که قابل تخصیص بهU
هستند را ازT
استخراج میکند.NonNullable<T>
:null
وundefined
را ازT
حذف میکند.Parameters<T>
: پارامترهای یک نوع تابعT
را به دست میآورد.ReturnType<T>
: نوع بازگشتی یک نوع تابعT
را به دست میآورد.InstanceType<T>
: نوع نمونه یک نوع تابع سازندهT
را به دست میآورد.
این انواع کاربردی ابزارهای قدرتمندی هستند که میتوانند دستکاریهای پیچیده نوع را ساده کنند. برای مثال، شما میتوانید 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
را خواهد داشت.
بهترین شیوهها و ملاحظات
- ساده نگه دارید: در حالی که انواع نگاشتی و انواع شرطی قدرتمند هستند، میتوانند کد شما را پیچیدهتر کنند. سعی کنید تبدیلات نوع خود را تا حد امکان ساده نگه دارید.
- از انواع کاربردی استفاده کنید: هر زمان که ممکن است از انواع کاربردی داخلی تایپاسکریپت استفاده کنید. آنها به خوبی تست شدهاند و میتوانند کد شما را ساده کنند.
- انواع خود را مستند کنید: تبدیلات نوع خود را به وضوح مستند کنید، به خصوص اگر پیچیده هستند. این به سایر توسعهدهندگان کمک میکند تا کد شما را درک کنند.
- انواع خود را تست کنید: از بررسی نوع تایپاسکریپت برای اطمینان از اینکه تبدیلات نوع شما همانطور که انتظار میرود کار میکنند، استفاده کنید. میتوانید تستهای واحدی برای تأیید رفتار انواع خود بنویسید.
- به عملکرد توجه کنید: تبدیلات نوع پیچیده میتوانند بر عملکرد کامپایلر تایپاسکریپت شما تأثیر بگذارند. به پیچیدگی انواع خود توجه داشته باشید و از محاسبات غیرضروری خودداری کنید.
نتیجهگیری
انواع نگاشتی و انواع شرطی ویژگیهای قدرتمندی در تایپاسکریپت هستند که به شما امکان میدهند تبدیلات نوع بسیار انعطافپذیر و گویایی ایجاد کنید. با تسلط بر این مفاهیم، میتوانید امنیت نوع، قابلیت نگهداری و کیفیت کلی برنامههای تایپاسکریپت خود را بهبود بخشید. از تبدیلات ساده مانند اختیاری یا فقط-خواندنی کردن خصوصیات گرفته تا تبدیلات بازگشتی پیچیده و منطق شرطی، این ویژگیها ابزارهایی را که برای ساخت برنامههای قوی و مقیاسپذیر نیاز دارید، فراهم میکنند. به کاوش و آزمایش با این ویژگیها ادامه دهید تا پتانسیل کامل آنها را آزاد کرده و به یک توسعهدهنده ماهرتر تایپاسکریپت تبدیل شوید.
همانطور که سفر تایپاسکریپت خود را ادامه میدهید، به یاد داشته باشید که از منابع فراوان موجود، از جمله مستندات رسمی تایپاسکریپت، جوامع آنلاین و پروژههای منبع باز، بهره ببرید. قدرت انواع نگاشتی و انواع شرطی را در آغوش بگیرید و برای مقابله با حتی چالشبرانگیزترین مشکلات مربوط به نوع، به خوبی مجهز خواهید شد.