فارسی

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

انواع شرطی تایپ‌اسکریپت برای طراحی پیشرفته API

در دنیای توسعه نرم‌افزار، ساخت APIها (رابط‌های برنامه‌نویسی کاربردی) یک عمل بنیادی است. یک API با طراحی خوب برای موفقیت هر برنامه‌ای، به ویژه هنگام کار با کاربران جهانی، حیاتی است. تایپ‌اسکریپت، با سیستم نوع قدرتمند خود، ابزارهایی را در اختیار توسعه‌دهندگان قرار می‌دهد تا APIهایی بسازند که نه تنها کاربردی، بلکه قوی، قابل نگهداری و فهم آسانی داشته باشند. در میان این ابزارها، انواع شرطی (Conditional Types) به عنوان یک مؤلفه کلیدی برای طراحی پیشرفته API برجسته هستند. این پست وبلاگ به بررسی پیچیدگی‌های انواع شرطی می‌پردازد و نشان می‌دهد چگونه می‌توان از آن‌ها برای ساخت APIهای سازگارتر و ایمن از نظر نوع (type-safe) استفاده کرد.

درک انواع شرطی

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


type ResultType = T extends string ? string : number;

در این مثال، `ResultType` یک نوع شرطی است. اگر نوع جنریک `T` از `string` ارث‌بری کند (قابل تخصیص به `string` باشد)، آنگاه نوع حاصل `string` خواهد بود؛ در غیر این صورت، `number` است. این مثال ساده مفهوم اصلی را نشان می‌دهد: بر اساس نوع ورودی، یک نوع خروجی متفاوت دریافت می‌کنیم.

سینتکس پایه و مثال‌ها

بیایید سینتکس را بیشتر بررسی کنیم:

در اینجا چند مثال دیگر برای تثبیت درک شما آورده شده است:


type StringOrNumber = T extends string ? string : number;

let a: StringOrNumber = 'hello'; // رشته
let b: StringOrNumber = 123; // عدد

در این مورد، ما یک نوع `StringOrNumber` را تعریف می‌کنیم که بسته به نوع ورودی `T`، یا `string` یا `number` خواهد بود. این مثال ساده قدرت انواع شرطی را در تعریف یک نوع بر اساس ویژگی‌های نوع دیگر نشان می‌دهد.


type Flatten = T extends (infer U)[] ? U : T;

let arr1: Flatten = 'hello'; // رشته
let arr2: Flatten = 123; // عدد

این نوع `Flatten` نوع عنصر را از یک آرایه استخراج می‌کند. این مثال از `infer` استفاده می‌کند که برای تعریف یک نوع در داخل شرط به کار می‌رود. `infer U` نوع `U` را از آرایه استنتاج می‌کند و اگر `T` یک آرایه باشد، نوع نتیجه `U` خواهد بود.

کاربردهای پیشرفته در طراحی API

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

۱. ایجاد انواع پاسخ پویا

یک API فرضی را در نظر بگیرید که داده‌های متفاوتی را بر اساس پارامترهای درخواست برمی‌گرداند. انواع شرطی به شما امکان می‌دهند تا نوع پاسخ را به صورت پویا مدل‌سازی کنید:


interface User {
  id: number;
  name: string;
  email: string;
}

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

type ApiResponse = 
  T extends 'user' ? User : Product;

function fetchData(type: T): ApiResponse {
  if (type === 'user') {
    return { id: 1, name: 'John Doe', email: 'john.doe@example.com' } as ApiResponse; // تایپ‌اسکریپت می‌داند که این یک User است
  } else {
    return { id: 1, name: 'Widget', price: 19.99 } as ApiResponse; // تایپ‌اسکریپت می‌داند که این یک Product است
  }
}

const userData = fetchData('user'); // userData از نوع User است
const productData = fetchData('product'); // productData از نوع Product است

در این مثال، نوع `ApiResponse` به صورت پویا بر اساس پارامتر ورودی `T` تغییر می‌کند. این امر ایمنی نوع را افزایش می‌دهد، زیرا تایپ‌اسکریپت ساختار دقیق داده‌های بازگشتی را بر اساس پارامتر `type` می‌داند. این کار نیاز به جایگزین‌های با ایمنی نوع کمتر مانند انواع union را از بین می‌برد.

۲. پیاده‌سازی مدیریت خطای ایمن از نظر نوع

APIها اغلب بسته به موفقیت یا شکست یک درخواست، شکل‌های پاسخ متفاوتی را برمی‌گردانند. انواع شرطی می‌توانند این سناریوها را به زیبایی مدل‌سازی کنند:


interface SuccessResponse {
  status: 'success';
  data: T;
}

interface ErrorResponse {
  status: 'error';
  message: string;
}

type ApiResult = T extends any ? SuccessResponse | ErrorResponse : never;

function processData(data: T, success: boolean): ApiResult {
  if (success) {
    return { status: 'success', data } as ApiResult;
  } else {
    return { status: 'error', message: 'An error occurred' } as ApiResult;
  }
}

const result1 = processData({ name: 'Test', value: 123 }, true); // SuccessResponse<{ name: string; value: number; }>
const result2 = processData({ name: 'Test', value: 123 }, false); // ErrorResponse

در اینجا، `ApiResult` ساختار پاسخ API را تعریف می‌کند که می‌تواند یا یک `SuccessResponse` یا یک `ErrorResponse` باشد. تابع `processData` تضمین می‌کند که نوع پاسخ صحیح بر اساس پارامتر `success` بازگردانده می‌شود.

۳. ایجاد Overloadهای انعطاف‌پذیر برای توابع

انواع شرطی همچنین می‌توانند در ترکیب با overloadهای توابع برای ایجاد APIهای بسیار سازگار استفاده شوند. Overloadهای توابع به یک تابع اجازه می‌دهند چندین امضا داشته باشد، که هر کدام دارای انواع پارامتر و انواع بازگشتی متفاوتی هستند. یک API را در نظر بگیرید که می‌تواند داده‌ها را از منابع مختلف دریافت کند:


function fetchDataOverload(resource: T): Promise;
function fetchDataOverload(resource: string): Promise;

async function fetchDataOverload(resource: string): Promise {
    if (resource === 'users') {
        // شبیه‌سازی دریافت کاربران از یک API
        return new Promise((resolve) => {
            setTimeout(() => resolve([{ id: 1, name: 'User 1', email: 'user1@example.com' }]), 100);
        });
    } else if (resource === 'products') {
        // شبیه‌سازی دریافت محصولات از یک API
        return new Promise((resolve) => {
            setTimeout(() => resolve([{ id: 1, name: 'Product 1', price: 10.00 }]), 100);
        });
    } else {
        // مدیریت سایر منابع یا خطاها
        return new Promise((resolve) => {
            setTimeout(() => resolve([]), 100);
        });
    }
}

(async () => {
    const users = await fetchDataOverload('users'); // users از نوع User[] است
    const products = await fetchDataOverload('products'); // products از نوع Product[] است
    console.log(users[0].name); // دسترسی ایمن به ویژگی‌های کاربر
    console.log(products[0].name); // دسترسی ایمن به ویژگی‌های محصول
})();

در اینجا، اولین overload مشخص می‌کند که اگر `resource` برابر با 'users' باشد، نوع بازگشتی `User[]` است. دومین overload مشخص می‌کند که اگر منبع 'products' باشد، نوع بازگشتی `Product[]` است. این تنظیمات امکان بررسی نوع دقیق‌تری را بر اساس ورودی‌های ارائه شده به تابع فراهم می‌کند و باعث بهبود تکمیل خودکار کد و تشخیص خطا می‌شود.

۴. ایجاد انواع ابزاری (Utility Types)

انواع شرطی ابزارهای قدرتمندی برای ساخت انواع ابزاری هستند که انواع موجود را تغییر می‌دهند. این انواع ابزاری می‌توانند برای دستکاری ساختارهای داده و ایجاد مؤلفه‌های قابل استفاده مجدد در یک API مفید باشند.


interface Person {
  name: string;
  age: number;
  address: {
    street: string;
    city: string;
    country: string;
  };
}

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

const readonlyPerson: DeepReadonly = {
  name: 'John',
  age: 30,
  address: {
    street: '123 Main St',
    city: 'Anytown',
    country: 'USA',
  },
};

// readonlyPerson.name = 'Jane'; // خطا: نمی‌توان به 'name' مقدار اختصاص داد زیرا یک ویژگی فقط-خواندنی است.
// readonlyPerson.address.street = '456 Oak Ave'; // خطا: نمی‌توان به 'street' مقدار اختصاص داد زیرا یک ویژگی فقط-خواندنی است.

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

۵. انتزاعی‌سازی داده‌های پاسخ API

در تعاملات API در دنیای واقعی، شما اغلب با ساختارهای پاسخ بسته‌بندی شده کار می‌کنید. انواع شرطی می‌توانند مدیریت پوسته‌های پاسخ مختلف را ساده کنند.


interface ApiResponseWrapper {
  data: T;
  meta: {
    total: number;
    page: number;
  };
}

type UnwrapApiResponse = T extends ApiResponseWrapper ? U : T;

function processApiResponse(response: ApiResponseWrapper): UnwrapApiResponse {
  return response.data;
}

interface ProductApiData {
  name: string;
  price: number;
}

const productResponse: ApiResponseWrapper = {
  data: {
    name: 'Example Product',
    price: 20,
  },
  meta: {
    total: 1,
    page: 1,
  },
};

const unwrappedProduct = processApiResponse(productResponse); // unwrappedProduct از نوع ProductApiData است

در این نمونه، `UnwrapApiResponse` نوع داخلی `data` را از `ApiResponseWrapper` استخراج می‌کند. این به مصرف‌کننده API اجازه می‌دهد تا با ساختار داده اصلی کار کند بدون اینکه همیشه مجبور به سر و کار داشتن با پوسته باشد. این برای تطبیق پاسخ‌های API به طور مداوم بسیار مفید است.

بهترین شیوه‌ها برای استفاده از انواع شرطی

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

مثال‌های دنیای واقعی و ملاحظات جهانی

بیایید برخی از سناریوهای دنیای واقعی را بررسی کنیم که در آن انواع شرطی می‌درخشند، به ویژه هنگام طراحی APIهایی که برای مخاطبان جهانی در نظر گرفته شده‌اند:

این مثال‌ها تطبیق‌پذیری انواع شرطی را در ایجاد APIهایی که به طور مؤثر جهانی‌سازی را مدیریت می‌کنند و نیازهای متنوع مخاطبان بین‌المللی را برآورده می‌کنند، برجسته می‌سازد. هنگام ساخت API برای مخاطبان جهانی، در نظر گرفتن مناطق زمانی، ارزها، فرمت‌های تاریخ و ترجیحات زبانی بسیار مهم است. با به کارگیری انواع شرطی، توسعه‌دهندگان می‌توانند APIهای سازگار و ایمن از نظر نوع ایجاد کنند که تجربه کاربری استثنایی را بدون توجه به مکان فراهم می‌کنند.

مشکلات احتمالی و نحوه اجتناب از آن‌ها

در حالی که انواع شرطی فوق‌العاده مفید هستند، مشکلات بالقوه‌ای نیز برای اجتناب وجود دارد:

نتیجه‌گیری

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