توسعه فرانت‌اند

الگوهای پیشرفته TypeScript برای توسعه‌دهندگان: از طراحی Type-safe تا معماری APIهای پایدار

تایپ‌اسکریپت در دستان یک توسعه‌دهنده جونیور، صرفاً یک «ابزارِ بررسیِ غلط‌هایِ تایپی» است؛ اما در دستان یک سنیور، ابزاری برای مهندسیِ قراردادهایِ کسب‌وکار است. وقتی از الگ...

1404/10/25 2 دقیقه مطالعه 1,789 بازدید

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

۱. Generics به‌عنوان موتور طراحی Type-safe API

بسیاری از توسعه‌دهندگان به Generics به چشمِ «متغیرهایِ نوع» نگاه می‌کنند، اما آن‌ها «پارامترهایِ معماری» هستند.

Generic Constraints (محدودسازی برای امنیت)

به‌جای استفاده از any در توابعِ داده‌ای، از extends استفاده کنید تا محدودیت‌هایِ دامنه را اعمال کنید.

مثال عملی در مدیریت محصولات فروشگاه:

interface BaseProduct { id: string; name: string; }
interface DigitalProduct extends BaseProduct { downloadLink: string; }

function processProduct(product: T) {
  return { ...product, processedAt: new Date() };
}

// اگر فیلد id نباشد، تایپ‌اسکریپت اجازه نمی‌دهد
processProduct({ name: 'موبایل' }); // Error: Property 'id' is missing

Default Generics برای بهبود DX

استفاده از مقادیر پیش‌فرض برای Generics باعث می‌شود تا حد امکان از نوشتنِ تایپ‌های طولانی پرهیز کنید.

interface ApiResponse {
  data: T;
  status: number;
}
// حالا می‌توانید به سادگی بنویسید: const res: ApiResponse;

۲. Conditional Types: if/else در سطح Type System

تایپ‌اسکریپت یک زبانِ برنامه‌نویسیِ کامل است (Turing-complete). ما می‌توانیم منطقِ تجاری را به سیستمِ نوع‌ها منتقل کنیم.

سناریوی فروشگاهی: بسته به نوعِ محصول، فیلدهایِ متفاوتی برایِ محاسبهِ وزنِ ارسال داریم:

type ShippingInfo = T extends 'physical' 
  ? { weight: number; dimensions: [number, number, number] }
  : { downloadLink: string };

const physicalProduct: ShippingInfo<'physical'> = { weight: 10, dimensions: [10, 10, 10] };

این کار باعث می‌شود خطاهایِ منطقیِ انسانی در هنگامِ توسعه، در زمانِ کامپایل (Compile-time) شناسایی شوند.

۳. Mapped Types و Template Literal Types

تولیدِ خودکارِ ساختارها از رویِ داده‌هایِ پایه، قدرتِ اصلیِ توسعه‌دهندگانِ ارشد است.

type ProductEvents = 'created' | 'updated' | 'deleted';
type ProductHandlers = {
  [K in `onProduct${Capitalize}`]: () => void;
};
// نتیجه: { onProductCreated: () => void; onProductUpdated: ... }

این الگو برای ساختنِ سیستم‌هایِ Event-driven در فروشگاه‌ها (مثل لاگ‌اندازیِ خودکارِ تغییرات) حیاتی است.

۴. استخراج دانش با infer

اگر تابعی دارید که دیتایِ پیچیده‌ای برمی‌گرداند، لازم نیست نوعِ آن را دستی بنویسید. اجازه دهید تایپ‌اسکریپت آن را استنتاج (Infer) کند.

type ReturnTypeWrapper = T extends (...args: any[]) => infer R ? R : never;

function getOrderDetails() { return { id: 1, total: 100 }; }
type Order = ReturnTypeWrapper; 
// Order حالا به‌طور خودکار: { id: number; total: number; }

۵. satisfies در مقابل as

بزرگترین اشتباهِ سنیورها، استفاده‌ی بیش‌ازحد از as است که تایپ‌چک را خاموش می‌کند. همیشه از satisfies استفاده کنید.

const cartConfig = {
  currency: 'IRR',
  tax: 0.09
} satisfies Record;

// اگر اینجا currency را فراموش کنید، TS هشدار می‌دهد. 
// اما اگر از 'as' استفاده می‌کردید، اصلاً متوجه نمی‌شدید.

۶. Branded Types (Nominal Typing)

در جاوااسکریپت همه چیز string است. این یعنی userId می‌تواند جایِ orderId قرار بگیرد. برای جلوگیری از این فاجعه در فروشگاه‌های بزرگ:

type UserId = string & { __brand: 'UserId' };
type OrderId = string & { __brand: 'OrderId' };

function processOrder(id: OrderId) { /* ... */ }

const userId = '123' as UserId;
processOrder(userId); // Error: Type 'UserId' is not assignable to 'OrderId'

این الگو، نرخِ باگ‌هایِ منطقی را در محیطِ عملیاتی تا ۸۰٪ کاهش می‌دهد.

۷. الگوهایِ Type-safe Error Handling

به‌جای استفاده از try-catchهای بی‌پایان که معلوم نیست چه چیزی را throw می‌کنند، از الگویِ Result استفاده کنید:

type Result = { ok: true; value: T } | { ok: false; error: E };

function checkout(): Result {
  // logic
}

۸. معماریِ نوع‌ها: Domain Types vs DTO

بزرگترین اشتباه در تیم‌هایِ میانی، استفاده از مدلِ دیتابیس در لایه‌یِ ویو است.

  • DTO (Data Transfer Object): ساختارِ خامِ دریافتی از API (مثلاً تاریخ به‌صورتِ string).
  • Domain Type: ساختارِ داخلیِ منطقِ شما (مثلاً تاریخ به‌صورتِ Date object).

همیشه بین این دو لایه با استفاده از یک تابعِ Mapper، یک مرزِ ایمن (Validation Boundary) ایجاد کنید.

۹. چک‌لیست سنیور TypeScript (برای Code Review)

قبل از اینکه کد را به مرحله‌یِ Pull Request برسانید، این ۱۰ سؤال را پاسخ دهید:

  1. آیا از any یا as استفاده شده است؟ (ممنوع!)
  2. آیا برایِ یک مدلِ ساده، بیش از حد از پیچیدگی‌هایِ Generic استفاده شده؟
  3. آیا لایه‌یِ Domain از لایه‌یِ DTO جدا شده است؟
  4. آیا از satisfies برایِ تنظیماتِ کانفیگ استفاده شده؟
  5. آیا نامِ نوع‌ها (Types) به اندازه کافی توصیفی هستند؟
  6. آیا در مواجهه با APIهایِ خارجی، از Validation (مثل Zod) در مرزِ ورودی استفاده شده؟
  7. آیا نوع‌هایِ پیچیده (Complex Types) به بخش‌هایِ کوچک‌تر تقسیم شده‌اند؟
  8. آیا کامنت‌هایِ کد، توضیح می‌دهند «چرا» این نوع ساخته شده؟
  9. آیا Branded Types برای شناسه‌ها (IDs) لحاظ شده‌اند؟
  10. آیا این کد در تست‌هایِ استاتیک (tsc) خطا نمی‌دهد؟

نتیجه‌گیری: تایپ‌اسکریپت به عنوان زبانِ طراحی

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


واکنش به این مقاله

اشتراک‌گذاری این مقاله:
دیدگاه‌ها 0
۰ / ۲۰۰۰

هنوز دیدگاهی ثبت نشده

اولین نفری باشید که نظر می‌دهد!