راهنمای جامع کلمهی کلیدی infer در تایپاسکریپت برای استخراج و دستکاری قدرتمند تایپها با استفاده از تایپهای شرطی و بررسی موارد استفاده پیشرفته.
تسلط بر infer در تایپاسکریپت: استخراج تایپ با تایپهای شرطی برای دستکاری پیشرفته تایپها
سیستم تایپ تایپاسکریپت فوقالعاده قدرتمند است و به توسعهدهندگان اجازه میدهد تا برنامههای قوی و قابل نگهداری بسازند. یکی از ویژگیهای کلیدی که این قدرت را ممکن میسازد، کلمهی کلیدی infer
است که در ترکیب با تایپهای شرطی استفاده میشود. این ترکیب مکانیزمی برای استخراج تایپهای خاص از ساختارهای تایپ پیچیده فراهم میکند. این پست وبلاگ به طور عمیق به کلمهی کلیدی infer
میپردازد، عملکرد آن را توضیح میدهد و موارد استفاده پیشرفته را به نمایش میگذارد. ما مثالهای عملی قابل استفاده در سناریوهای مختلف توسعه نرمافزار، از تعامل با API تا دستکاری ساختارهای داده پیچیده را بررسی خواهیم کرد.
تایپهای شرطی (Conditional Types) چه هستند؟
قبل از اینکه به سراغ infer
برویم، بیایید به سرعت تایپهای شرطی را مرور کنیم. تایپهای شرطی در تایپاسکریپت به شما اجازه میدهند تا یک تایپ را بر اساس یک شرط تعریف کنید، مشابه عملگر سهتایی (ternary operator) در جاوااسکریپت. سینتکس اصلی آن به این صورت است:
T extends U ? X : Y
این به این صورت خوانده میشود: «اگر تایپ T
قابل انتساب به تایپ U
باشد، آنگاه تایپ برابر با X
است؛ در غیر این صورت، تایپ برابر با Y
است.»
مثال:
type IsString = T extends string ? true : false;
type StringResult = IsString; // type StringResult = true
type NumberResult = IsString; // type NumberResult = false
معرفی کلمهی کلیدی infer
کلمهی کلیدی infer
در بخش extends
یک تایپ شرطی برای تعریف یک متغیر تایپ استفاده میشود که میتواند از تایپ در حال بررسی استنتاج (infer) شود. در اصل، به شما اجازه میدهد تا بخشی از یک تایپ را برای استفادههای بعدی «ضبط» کنید.
سینتکس اصلی:
type MyType = T extends (infer U) ? U : never;
در این مثال، اگر T
به تایپی قابل انتساب باشد، تایپاسکریپت تلاش میکند تا تایپ U
را استنتاج کند. اگر استنتاج موفقیتآمیز باشد، تایپ حاصل U
خواهد بود؛ در غیر این صورت، never
خواهد بود.
مثالهای ساده از infer
۱. استنتاج تایپ خروجی یک تابع
یک مورد استفاده رایج، استنتاج تایپ خروجی یک تابع است:
type ReturnType any> = T extends (...args: any) => infer R ? R : any;
function add(a: number, b: number): number {
return a + b;
}
type AddReturnType = ReturnType; // type AddReturnType = number
function greet(name: string): string {
return `Hello, ${name}!`;
}
type GreetReturnType = ReturnType; // type GreetReturnType = string
در این مثال، ReturnType
یک تایپ تابع T
را به عنوان ورودی میگیرد. این تایپ بررسی میکند که آیا T
به یک تابع که هر آرگومانی را میپذیرد و مقداری را برمیگرداند قابل انتساب است یا خیر. اگر چنین باشد، تایپ خروجی را به عنوان R
استنتاج کرده و آن را برمیگرداند. در غیر این صورت، any
را برمیگرداند.
۲. استنتاج تایپ عناصر آرایه
سناریوی مفید دیگر، استخراج تایپ عناصر از یک آرایه است:
type ArrayElementType = T extends (infer U)[] ? U : never;
type NumberArrayType = ArrayElementType; // type NumberArrayType = number
type StringArrayType = ArrayElementType; // type StringArrayType = string
type MixedArrayType = ArrayElementType<(string | number)[]>; // type MixedArrayType = string | number
type NotAnArrayType = ArrayElementType; // type NotAnArrayType = never
در اینجا، ArrayElementType
بررسی میکند که آیا T
یک تایپ آرایه است یا خیر. اگر باشد، تایپ عناصر را به عنوان U
استنتاج کرده و آن را برمیگرداند. در غیر این صورت، never
را برمیگرداند.
موارد استفاده پیشرفته از infer
۱. استنتاج پارامترهای یک سازنده (Constructor)
شما میتوانید از infer
برای استخراج تایپهای پارامترهای یک تابع سازنده استفاده کنید:
type ConstructorParameters any> = T extends new (...args: infer P) => any ? P : never;
class Person {
constructor(public name: string, public age: number) {}
}
type PersonConstructorParams = ConstructorParameters; // type PersonConstructorParams = [string, number]
class Point {
constructor(public x: number, public y: number) {}
}
type PointConstructorParams = ConstructorParameters; // type PointConstructorParams = [number, number]
در این مورد، ConstructorParameters
یک تایپ تابع سازنده T
را میگیرد. این تایپ، تایپهای پارامترهای سازنده را به عنوان P
استنتاج کرده و آنها را به صورت یک تاپل (tuple) برمیگرداند.
۲. استخراج پراپرتیها از تایپهای آبجکت
infer
همچنین میتواند برای استخراج پراپرتیهای خاص از تایپهای آبجکت با استفاده از تایپهای نگاشتی (mapped types) و تایپهای شرطی استفاده شود:
type PickByType = {
[P in K as T[P] extends U ? P : never]: T[P];
};
interface User {
id: number;
name: string;
age: number;
email: string;
isActive: boolean;
}
type StringProperties = PickByType; // type StringProperties = { name: string; email: string; }
type NumberProperties = PickByType; // type NumberProperties = { id: number; age: number; }
// یک اینترفیس برای نمایش مختصات جغرافیایی.
interface GeoCoordinates {
latitude: number;
longitude: number;
altitude: number;
country: string;
city: string;
timezone: string;
}
type NumberCoordinateProperties = PickByType; // type NumberCoordinateProperties = { latitude: number; longitude: number; altitude: number; }
در اینجا، PickByType
یک تایپ جدید ایجاد میکند که فقط شامل پراپرتیهای T
(با کلیدهایی در K
) است که مقادیر آنها به تایپ U
قابل انتساب هستند. تایپ نگاشتی روی کلیدهای T
پیمایش میکند و تایپ شرطی کلیدهایی را که با تایپ مشخص شده مطابقت ندارند، فیلتر میکند.
۳. کار با Promiseها
شما میتوانید تایپ resolved شده یک Promise
را استنتاج کنید:
type Awaited = T extends Promise ? U : T;
async function fetchData(): Promise {
return 'Data from API';
}
type FetchDataType = Awaited>; // type FetchDataType = string
async function fetchNumbers(): Promise {
return [1, 2, 3];
}
type FetchedNumbersType = Awaited>; //type FetchedNumbersType = number[]
تایپ Awaited
یک تایپ T
را میگیرد که انتظار میرود یک Promise باشد. سپس این تایپ، تایپ resolved شده U
از Promise را استنتاج کرده و آن را برمیگرداند. اگر T
یک promise نباشد، خود T را برمیگرداند. این یک تایپ کمکی داخلی (built-in utility type) در نسخههای جدیدتر تایپاسکریپت است.
۴. استخراج تایپ از آرایهای از Promiseها
ترکیب Awaited
و استنتاج تایپ آرایه به شما اجازه میدهد تا تایپ resolved شده توسط آرایهای از Promiseها را استنتاج کنید. این امر به ویژه هنگام کار با Promise.all
مفید است.
type PromiseArrayReturnType[]> = {
[K in keyof T]: Awaited;
};
async function getUSDRate(): Promise {
return 0.0069;
}
async function getEURRate(): Promise {
return 0.0064;
}
const rates = [getUSDRate(), getEURRate()];
type RatesType = PromiseArrayReturnType;
// type RatesType = [number, number]
این مثال ابتدا دو تابع ناهمزمان getUSDRate
و getEURRate
را تعریف میکند که دریافت نرخ ارز را شبیهسازی میکنند. سپس تایپ کمکی PromiseArrayReturnType
تایپ resolved شده از هر Promise
در آرایه را استخراج میکند، که منجر به یک تایپ تاپل میشود که هر عنصر آن، تایپ await شدهی Promise مربوطه است.
مثالهای عملی در حوزههای مختلف
۱. اپلیکیشن تجارت الکترونیک
یک اپلیکیشن تجارت الکترونیک را در نظر بگیرید که در آن جزئیات محصول را از یک API دریافت میکنید. میتوانید از infer
برای استخراج تایپ دادههای محصول استفاده کنید:
interface Product {
id: number;
name: string;
price: number;
description: string;
imageUrl: string;
category: string;
rating: number;
countryOfOrigin: string;
}
async function fetchProduct(productId: number): Promise {
// شبیهسازی فراخوانی API
return new Promise((resolve) => {
setTimeout(() => {
resolve({
id: productId,
name: 'Example Product',
price: 29.99,
description: 'A sample product',
imageUrl: 'https://example.com/image.jpg',
category: 'Electronics',
rating: 4.5,
countryOfOrigin: 'Canada'
});
}, 500);
});
}
type ProductType = Awaited>; // type ProductType = Product
function displayProductDetails(product: ProductType) {
console.log(`Product Name: ${product.name}`);
console.log(`Price: ${product.price} ${product.countryOfOrigin === 'Canada' ? 'CAD' : (product.countryOfOrigin === 'USA' ? 'USD' : 'EUR')}`);
}
fetchProduct(123).then(displayProductDetails);
در این مثال، ما یک اینترفیس Product
و یک تابع fetchProduct
تعریف میکنیم که جزئیات محصول را از یک API دریافت میکند. ما از Awaited
و ReturnType
برای استخراج تایپ Product
از تایپ خروجی تابع fetchProduct
استفاده میکنیم، که به ما امکان میدهد تا تایپ تابع displayProductDetails
را بررسی کنیم.
۲. بینالمللیسازی (i18n)
فرض کنید یک تابع ترجمه دارید که رشتههای مختلفی را بر اساس زبان محلی (locale) برمیگرداند. میتوانید از infer
برای استخراج تایپ خروجی این تابع برای ایمنی تایپ (type safety) استفاده کنید:
interface Translations {
greeting: string;
farewell: string;
welcomeMessage: (name: string) => string;
}
const enTranslations: Translations = {
greeting: 'Hello',
farewell: 'Goodbye',
welcomeMessage: (name: string) => `Welcome, ${name}!`,
};
const frTranslations: Translations = {
greeting: 'Bonjour',
farewell: 'Au revoir',
welcomeMessage: (name: string) => `Bienvenue, ${name}!`,
};
function getTranslation(locale: 'en' | 'fr'): Translations {
return locale === 'en' ? enTranslations : frTranslations;
}
type TranslationType = ReturnType;
function greetUser(locale: 'en' | 'fr', name: string) {
const translations = getTranslation(locale);
console.log(translations.welcomeMessage(name));
}
greetUser('fr', 'Jean'); // Output: Bienvenue, Jean!
در اینجا، TranslationType
به عنوان اینترفیس Translations
استنتاج میشود، که تضمین میکند تابع greetUser
اطلاعات تایپ صحیحی برای دسترسی به رشتههای ترجمه شده دارد.
۳. مدیریت پاسخ API
هنگام کار با APIها، ساختار پاسخ میتواند پیچیده باشد. infer
میتواند به استخراج تایپهای داده خاص از پاسخهای API تودرتو کمک کند:
interface ApiResponse {
status: number;
data: T;
message?: string;
}
interface UserData {
id: number;
username: string;
email: string;
profile: {
firstName: string;
lastName: string;
country: string;
language: string;
}
}
async function fetchUser(userId: number): Promise> {
// شبیهسازی فراخوانی API
return new Promise((resolve) => {
setTimeout(() => {
resolve({
status: 200,
data: {
id: userId,
username: 'johndoe',
email: 'john.doe@example.com',
profile: {
firstName: 'John',
lastName: 'Doe',
country: 'USA',
language: 'en'
}
}
});
}, 500);
});
}
type UserApiResponse = Awaited>;
type UserProfileType = UserApiResponse['data']['profile'];
function displayUserProfile(profile: UserProfileType) {
console.log(`Name: ${profile.firstName} ${profile.lastName}`);
console.log(`Country: ${profile.country}`);
}
fetchUser(123).then((response) => {
if (response.status === 200) {
displayUserProfile(response.data.profile);
}
});
در این مثال، ما یک اینترفیس ApiResponse
و یک اینترفیس UserData
تعریف میکنیم. ما از infer
و نمایهسازی تایپ (type indexing) برای استخراج UserProfileType
از پاسخ API استفاده میکنیم، که تضمین میکند تابع displayUserProfile
تایپ صحیحی را دریافت میکند.
بهترین شیوهها برای استفاده از infer
- ساده نگه دارید: از
infer
فقط در مواقع ضروری استفاده کنید. استفاده بیش از حد از آن میتواند خواندن و درک کد شما را دشوارتر کند. - تایپهای خود را مستند کنید: کامنتهایی اضافه کنید تا توضیح دهید تایپهای شرطی و عبارات
infer
شما چه کاری انجام میدهند. - تایپهای خود را تست کنید: از بررسی تایپ تایپاسکریپت برای اطمینان از اینکه تایپهای شما همانطور که انتظار میرود رفتار میکنند، استفاده کنید.
- عملکرد را در نظر بگیرید: تایپهای شرطی پیچیده گاهی اوقات میتوانند بر زمان کامپایل تأثیر بگذارند. به پیچیدگی تایپهای خود توجه داشته باشید.
- از تایپهای کمکی استفاده کنید: تایپاسکریپت چندین تایپ کمکی داخلی (مانند
ReturnType
وAwaited
) ارائه میدهد که میتوانند کد شما را ساده کرده و نیاز به عباراتinfer
سفارشی را کاهش دهند.
اشتباهات رایج
- استنتاج نادرست: گاهی اوقات، تایپاسکریپت ممکن است تایپی را استنتاج کند که انتظارش را ندارید. تعاریف و شرایط تایپ خود را دوباره بررسی کنید.
- وابستگیهای دَوَرانی: هنگام تعریف تایپهای بازگشتی با استفاده از
infer
مراقب باشید، زیرا میتوانند منجر به وابستگیهای دَوَرانی و خطاهای کامپایل شوند. - تایپهای بیش از حد پیچیده: از ایجاد تایپهای شرطی بیش از حد پیچیده که درک و نگهداری آنها دشوار است، خودداری کنید. آنها را به تایپهای کوچکتر و قابل مدیریتتر تقسیم کنید.
جایگزینهای infer
در حالی که infer
ابزار قدرتمندی است، شرایطی وجود دارد که رویکردهای جایگزین ممکن است مناسبتر باشند:
- ادعای تایپ (Type Assertions): در برخی موارد، میتوانید از ادعای تایپ برای مشخص کردن صریح تایپ یک مقدار به جای استنتاج آن استفاده کنید. با این حال، در استفاده از ادعای تایپ احتیاط کنید، زیرا میتوانند بررسی تایپ را دور بزنند.
- محافظهای تایپ (Type Guards): میتوان از محافظهای تایپ برای محدود کردن تایپ یک مقدار بر اساس بررسیهای زمان اجرا استفاده کرد. این روش زمانی مفید است که نیاز به مدیریت تایپهای مختلف بر اساس شرایط زمان اجرا دارید.
- تایپهای کمکی (Utility Types): تایپاسکریپت مجموعه غنی از تایپهای کمکی را ارائه میدهد که میتوانند بسیاری از وظایف رایج دستکاری تایپ را بدون نیاز به عبارات
infer
سفارشی انجام دهند.
نتیجهگیری
کلمهی کلیدی infer
در تایپاسکریپت، هنگامی که با تایپهای شرطی ترکیب میشود، قابلیتهای پیشرفته دستکاری تایپ را باز میکند. این به شما امکان میدهد تا تایپهای خاص را از ساختارهای تایپ پیچیده استخراج کنید و به شما این امکان را میدهد که کدی قویتر، قابل نگهداریتر و با ایمنی تایپ بالاتر بنویسید. از استنتاج تایپهای خروجی تابع گرفته تا استخراج پراپرتیها از تایپهای آبجکت، امکانات بسیار گسترده هستند. با درک اصول و بهترین شیوههای ذکر شده در این راهنما، میتوانید از infer
به طور کامل بهرهمند شوید و مهارتهای تایپاسکریپت خود را ارتقا دهید. به یاد داشته باشید که تایپهای خود را مستند کنید، آنها را به طور کامل تست کنید و در صورت لزوم رویکردهای جایگزین را در نظر بگیرید. تسلط بر infer
شما را قادر میسازد تا کدهای تایپاسکریپت واقعاً گویا و قدرتمندی بنویسید که در نهایت منجر به نرمافزار بهتری میشود.