در بازبینی یک پروژه‌ی تیمی که روی یک پلتفرم SaaS کار می‌کرد، با یک مورد عجیب روبرو شدم: تیمی که از TypeScript استفاده می‌کرد، همچنان به‌طور مداوم با باگ‌های «خاصیت ناموجود» درگیر بود. کامپایلر بی‌خطا کامپایل می‌شد، تست‌های واحد پاس می‌شدند، اما در production، هر چند هفته یک باگ جدید ظاهر می‌شد که ریشه‌اش در ناهماهنگی اینترفیس‌ها بود. وقتی فایل تایپ‌های مشترک را باز کردم، فهمیدم که تیم تعریف interface را نه به‌عنوان یک قرارداد معنادار، بلکه به‌عنوان یک برچسبِ زودگذر می‌دید. اینترفیس‌ها در جاهای مختلف با ساختارهای مشابه اما نام‌های متفاوت تعریف شده بودند و هیچ‌کدام یک منبع واحد حقیقت (Single Source of Truth) نداشتند. آن تجربه، فهم من از اینترفیس در TypeScript را تغییر داد: interface یک ابزار تایپ نیست؛ یک قرارداد معماری است که اگر درست طراحی شود، کل پروژه را منسجم نگه می‌دارد و اگر اشتباه به‌کار رود، خودش تبدیل به منبع باگ می‌شود.

interface در TypeScript یکی از دو راه اصلی (به‌همراه type) برای تعریف شکل یک شیء است. اما تفاوت‌های ظریف بین این دو، در پروژه‌های بزرگ اثر محسوسی می‌گذارد. اگر در مسیر آموزش تایپ اسکریپت از صفر هستید و مقالات تایپ ها در تایپ اسکریپت و تفاوت تایپ اسکریپت و جاوااسکریپت را خوانده‌اید، این نوشته یک لایه‌ی معماری از همان مباحث را باز می‌کند.

چرا interface فراتر از یک ابزار تایپ است؟

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

  • نقش قرارداد (Contract): interface می‌گوید «هر چیزی که خودش را User می‌نامد، باید این ساختار را داشته باشد». این تعریف، هم بین بخش‌های مختلف کد، هم بین اعضای تیم، و هم بین فرانت‌اند و بک‌اند یک زبان مشترک می‌سازد. مطالعه‌ی این لایه در تفاوت تایپ اسکریپت و جاوااسکریپت آمده است.
  • نقش مستندات زنده: وقتی یک توسعه‌دهنده‌ی جدید به پروژه اضافه می‌شود، فایل تایپ‌ها نقشه‌ی دامنه‌ی کسب‌وکار را به او می‌دهد. به شرطی که interfaceها درست نام‌گذاری شده و به‌درستی سازماندهی شده باشند.
  • نقش مرز در معماری: interface می‌تواند مرزهای بین لایه‌های پروژه (Domain, Application, Infrastructure) را مشخص کند. یک interface که در لایه‌ی Domain تعریف شده، می‌تواند در سطوح مختلف بدون نشتِ جزئیات به بیرون استفاده شود.

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

اینترفیس در TypeScript، مثل قراردادهای رسمی بین شرکت‌هاست: هر چه دقیق‌تر و شفاف‌تر باشد، همکاری روان‌تر می‌شود؛ هر چه مبهم‌تر باشد، هزینه‌ی شکست بالا می‌رود.

ساختار پایه و ویژگی‌های اختیاری

ساختار پایه‌ی interface بسیار ساده است:

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

هر تایپی که این ساختار را داشته باشد، از نظر TS قابل استفاده به‌عنوان User است. این یعنی interface، یک قرارداد ساختاری (structural) است، نه یک برچسب اسمی. تفاوت این نکته با زبان‌هایی مثل Java، در جای خود مهم است که در بخش‌های بعدی به آن می‌رسیم.

ویژگی‌های اختیاری

با ?، یک خاصیت را اختیاری می‌کنید:

interface User {
  id: number;
  name: string;
  email?: string;      // اختیاری
  phone?: string;      // اختیاری
}

در پروژه‌های واقعی، این ویژگی ابزار اصلی برای مدل‌سازی داده‌هایی است که ممکن است کامل نباشند. مثلاً یک User در مرحله‌ی ثبت‌نام، فقط id و name دارد و بعد از تکمیل پروفایل، ایمیل و تلفن هم اضافه می‌شوند. با Optional، می‌توانید هر دو حالت را در یک interface پوشش دهید.

ویژگی‌های فقط‌خواندنی

با readonly، از تغییر مقدار بعد از ساخت جلوگیری می‌کنید:

interface Order {
  readonly id: number;
  readonly createdAt: Date;
  status: string;      // قابل تغییر
}

const order: Order = {
  id: 1,
  createdAt: new Date(),
  status: "pending"
};

order.status = "approved";   // مجاز
order.id = 2;                // خطای کامپایل!

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

Optional و Readonly در قراردادهای واقعی

ترکیب Optional و Readonly در interface، الگوهای زیادی می‌سازد که در پروژه‌ها پرکاربرد هستند:

۱. مدل‌سازی داده‌های مرحله‌ای

interface RegistrationState {
  readonly email: string;
  name?: string;
  password?: string;
  isConfirmed?: boolean;
}

در یک فرایند چندمرحله‌ای (ثبت‌نام، سفارش، پرسش‌نامه)، این الگو اجازه می‌دهد یک interface واحد برای تمام مراحل داشته باشید.

۲. تنظیمات با مقادیر پیش‌فرض

interface AppConfig {
  readonly env: "development" | "production";
  theme?: "light" | "dark";
  debug?: boolean;
  apiUrl?: string;
}

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

۳. نهادهای دامنه با شناسه‌ی ثابت

interface Product {
  readonly id: string;
  readonly sku: string;
  name: string;
  price: number;
  stock: number;
}

در پروژه‌های فروشگاهی، id و sku هیچ‌وقت بعد از ساخت تغییر نمی‌کنند. علامت‌گذاری این دو به‌عنوان readonly یک لایه‌ی محافظت پنهان اضافه می‌کند.

Extends و ارث‌بری چندگانه

interface در TS می‌تواند از چند interface دیگر ارث‌بری کند — چیزی که در زبان‌های کلاس-محور معمولاً غیرممکن است:

interface Timestamps {
  createdAt: Date;
  updatedAt: Date;
}

interface SoftDelete {
  deletedAt?: Date;
}

interface Entity extends Timestamps, SoftDelete {
  id: number;
}

// Entity شامل id، createdAt، updatedAt و deletedAt است

در پروژه‌ای که یک سیستم مدیریت محتوا داشتیم، این الگو باعث شد که در تمام نهادهای دامنه (Post, Page, Category)، ساختارهای createdAt و updatedAt به‌طور یکسان و بدون تکرار تعریف شوند. قبل از این الگو، هر نهاد این فیلدها را جداگانه تعریف می‌کرد و تغییر یک نام در همه‌ی آن‌ها نیاز به بازبینی دستی داشت.

Extends از Type

یک interface همچنین می‌تواند از یک type با ساختار شیء ارث‌بری کند:

type BaseEntity = {
  id: number;
  createdAt: Date;
};

interface User extends BaseEntity {
  name: string;
  email: string;
}

نکته‌ی مهم: interface فقط می‌تواند از یک شیء (interface یا type شیئی) ارث‌بری کند. از Union یا Intersection نمی‌تواند.

Extends در برابر Intersection

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

معیارExtendsIntersection (&)
نحوinterface A extends B {}type A = B & C;
خواناییبالاتر در interfaceانعطاف‌پذیرتر در type
تعارض خاصیتخطای کامپایل واضحتایپ‌ها ادغام می‌شوند (نه همیشه)
تناسبتوسعه‌ی موجودیت‌های دامنهترکیب ترکیبی تایپ‌های ناهمگن

قاعده‌ی من در پروژه‌ها: برای نهادهای دامنه که ساختار پایه‌ای مشترک دارند، از Extends استفاده کنید. برای ترکیب تایپ‌های مختلف (مثلاً یک شیء با یک Union) از Intersection استفاده کنید.

Declaration Merging و کاربرد آن

یکی از ویژگی‌های منحصربه‌فرد interface در TS، قابلیت Declaration Merging است. یعنی اگر دو interface با یک نام در یک scope تعریف کنید، TS آن‌ها را در یک interface واحد ادغام می‌کند:

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

interface User {
  email: string;
}

// User حالا شامل id، name و email است

این ویژگی هم قدرتمند است و هم خطرناک. کاربردهای واقعی:

۱. گسترش تایپ‌های کتابخانه‌های خارجی

وقتی از یک کتابخانه استفاده می‌کنید و می‌خواهید خاصیتی به یک interface آماده اضافه کنید:

// در جایی از پروژه
declare module "express-serve-static-core" {
  interface Request {
    user?: User;
  }
}

این الگو در پروژه‌های Node.js با Express بسیار پرکاربرد است. بدون Declaration Merging، مجبور بودید تایپ‌های کتابخانه را override کنید — کاری که بسیار شکننده است.

۲. سازماندهی تایپ‌ها در چند فایل

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

قاعده‌ی من در پروژه‌های داخلی: از Declaration Merging فقط برای گسترش تایپ‌های کتابخانه‌های خارجی استفاده کنید. در کد داخلی، همه‌ی interface را در یک نقطه تعریف کنید.

Declaration Merging در TypeScript یک ابزار قدرتمند است، اما مثل تمام ابزارهای قدرتمند، در دست نامناسب می‌تواند شمشیری باشد که به خودی پروژه آسیب می‌زند.

Interface یا Type: تصمیم دقیق

این سؤال در تیم‌های TS یک سؤال همیشگی است. پاسخ دقیق، بستگی به موقعیت دارد. جدول زیر مرجع سریع است:

سناریوInterfaceType
تعریف شکل شیءانتخاب اولگزینه‌ی جایگزین
ارث‌بری چندگانهبا extends A, Bبا Intersection
Declaration Mergingپشتیبانی می‌کندپشتیبانی نمی‌کند
Union Typeپشتیبانی نمی‌کندپشتیبانی می‌کند
Tupleپشتیبانی نمی‌کندپشتیبانی می‌کند
تایپ‌های Primitiveپشتیبانی نمی‌کندپشتیبانی می‌کند
تایپ‌های Conditionپشتیبانی نمی‌کندپشتیبانی می‌کند
قالب‌های عمومیپشتیبانی می‌کندپشتیبانی می‌کند

پیشنهاد عملی من در پروژه‌ها:

  • برای شکل اشیاء و کلاس‌ها: interface. حتی اگر با type هم بتوانید، interface برای این کار طراحی شده است.
  • برای Union، Tuple، Primitive، تایپ‌های پیچیده: type. چون interface این‌ها را پشتیبانی نمی‌کند.
  • برای کتابخانه‌های عمومی: interface. چون کاربران کتابخانه می‌توانند با Declaration Merging، تایپ شما را گسترش دهند.
  • برای تایپ‌های محلی یک فایل: بستگی دارد. اگر فقط یک شیء ساده است، type کمی کوتاه‌تر است. اما در یک تیم با قواعد مشخص، یکی از دو انتخاب را برای همه‌ی موارد یکسان نگه دارید.

در پروژه‌ای که یک تیم فنی روی یک پروژه‌ی چندساله کار می‌کرد، قاعده‌ی «interface برای اشیاء، type برای بقیه» را اعمال کردیم. نتیجه: کدبیس یکدست‌تر، بازبینی سریع‌تر، و کمتر شدن بحث‌های بی‌پایان در جلسات. مطالعه‌ی بیشتر در آموزش تایپ اسکریپت از صفر.

اینترفیس برای توابع و کلاس‌ها

interface فقط برای اشیاء نیست. از آن می‌توانید برای توصیف توابع و کلاس‌ها هم استفاده کنید:

Function Interface

interface Comparator {
  (a: string, b: string): number;
}

const compare: Comparator = (a, b) => a.localeCompare(b);

این الگو در پروژه‌ها برای تعریف callbackهای مشترک کاربرد زیادی دارد. مثلاً در یک سیستم مرتب‌سازی، می‌توانید یک interface برای compare تعریف کنید که در چند جای مختلف استفاده شود.

Class Interface

interface Repository<T> {
  findById(id: number): Promise<T | null>;
  save(entity: T): Promise<T>;
  delete(id: number): Promise<void>;
}

class UserRepository implements Repository<User> {
  async findById(id: number): Promise<User | null> {
    // پیاده‌سازی
  }
  async save(user: User): Promise<User> {
    // پیاده‌سازی
  }
  async delete(id: number): Promise<void> {
    // پیاده‌سازی
  }
}

الگوی Repository یکی از پرکاربردترین الگوهای معماری است و در TS به‌طور طبیعی با interface پیاده‌سازی می‌شود. مزیت: می‌توانید در تست‌ها، یک پیاده‌سازی mock از همان interface بسازید بدون اینکه تست به جزئیات پیاده‌سازی وابسته باشد. مطالعه‌ی بیشتر در Generics در تایپ اسکریپت.

Index Signature و Record

در بعضی سناریوها، ساختار شیء در زمان کامپایل مشخص نیست. مثلاً یک شیء که کلیدهایش نام‌های پویا هستند. اینجا از Index Signature استفاده می‌کنیم:

interface Dictionary {
  [key: string]: number;
}

const scores: Dictionary = {
  ali: 85,
  sara: 92,
  reza: 78
};

Index Signature می‌گوید «هر کلید از تایپ X با مقدار از تایپ Y». این ابزار در پروژه‌ها برای موارد زیر کاربرد دارد:

  • دیکشنری‌ها و نقشه‌ها: نگاشت کلید به مقدار.
  • پاسخ‌های API با ساختار پویا: وقتی کلیدهای پاسخ از قبل مشخص نیستند.
  • Cache و Memoization: ساختار key-value برای ذخیره‌سازی موقت.

Index Signature در برابر Record

TS یک Utility Type به نام Record دارد که معادل مدرن‌تر Index Signature است:

// با Index Signature
interface Dict1 {
  [key: string]: number;
}

// با Record
type Dict2 = Record<string, number>;

توصیه‌ی من در پروژه‌ها: اگر تمام کلیدها از یک تایپ هستند، از Record استفاده کنید چون کوتاه‌تر و خواناتر است. اگر می‌خواهید برخی کلیدها را به‌طور صریح تعریف کنید (بعضی اجباری، بعضی پویا)، از ترکیب interface و Index Signature استفاده کنید. مطالعه‌ی بیشتر در تایپ ها در تایپ اسکریپت.

الگوهای واقعی در پروژه‌ها

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

  1. Interface به‌عنوان قرارداد لایه‌ی Repository: در یک پروژه‌ی فروشگاهی، لایه‌ی دسترسی به داده با Repository interface پیاده‌سازی شده بود. این امکان را می‌داد که در تست‌های واحد، نسخه‌ی In-Memory از همان Repository بسازیم. تست‌های واحد بدون نیاز به پایگاه‌داده اجرا می‌شدند.
  2. Interface به‌عنوان قرارداد API: در یک پروژه‌ی React که با چند API خارجی کار می‌کرد، برای هر سرویس یک interface تعریف کرده بودیم. این الگو در زمان تغییر سرویس، فقط نیاز به بازنویسی پیاده‌سازی داشت و کد مصرف‌کننده تغییر نمی‌کرد.
  3. Interface برای State Management: در یک اپلیکیشن React که از Redux استفاده می‌کرد، هر Action یک interface داشت که با Discriminated Union ترکیب می‌شد. این الگو کامپایلر را وادار می‌کرد که تمام حالت‌های ممکن را در reducer پوشش دهیم.

اشتباهاتی که در پروژه‌ها دیدم

  • تعریف چند interface با نام‌های مشابه: User، UserData، UserInfo، UserPayload. اگر تفاوت معناداری ندارند، یکی را انتخاب کنید و به آن پایبند بمانید. در پروژه‌ای که این مسئله را بازبینی کردیم، بیش از ۳۰ interface با نام‌های مشابه در فایل‌های مختلف پیدا شد.
  • استفاده از interface برای تایپ‌های Primitive: interface ID extends string {}. این کار نه‌فقط غیرضروری است بلکه در بعضی موارد رفتار غیرمنتظره دارد. برای این تایپ‌ها از type استفاده کنید.
  • Optional بی‌رویه: اگر همه‌ی خاصیت‌های یک interface اختیاری باشند، تایپ‌چکینگ تقریباً بی‌اثر می‌شود. از Optional فقط جایی استفاده کنید که واقعاً برخی مقادیر ممکن است نباشند.
  • Readonly بیش‌ازحد: در بعضی پروژه‌ها، همه‌ی خاصیت‌ها readonly هستند حتی جایی که منطقاً قابل تغییرند. این باعث می‌شود توسعه‌دهنده‌ها مجبور شوند با Type Assertion، readonly را دور بزنند — که از بین رفتن محافظت است.
  • مخلوط کردن interface و type بدون قاعده: در پروژه‌ای که یکی با interface و یکی با type تعریف شده بود، کدبیس ناهماهنگ و دشوار برای نگهداری بود. یک قاعده‌ی مشخص در تیم داشته باشید.
  • Declaration Merging در کد داخلی: استفاده از این ویژگی در پروژه‌ی خودتان می‌تواند به کابوس تبدیل شود چون تعریف‌ها در فایل‌های مختلف پراکنده می‌شوند و فهم کل interface دشوار می‌شود. این ویژگی را برای گسترش کتابخانه‌های خارجی نگه دارید.
  • نبود فایل تایپ مرکزی: در پروژه‌های بزرگ، تعریف interface در محل استفاده (کنار فایل کامپوننت)، باعث تکرار و ناهماهنگی می‌شود. یک فایل یا پوشه‌ی مشترک برای interfaceهای دامنه داشته باشید.
  • استفاده از interface به‌جای تایپ دقیق‌تر: بعضی توسعه‌دهنده‌ها به‌جای استفاده از Literal Types یا Union، همه‌چیز را string یا number می‌گیرند. مثلاً status: string به‌جای status: "pending" | "approved". این کار تمام مزیت تایپ‌چکینگ را از بین می‌برد. مطالعه‌ی بیشتر در تایپ ها در تایپ اسکریپت.

بخشی از این اشتباهات در خطاهای رایج تایپ اسکریپت و اشتباهات رایج توسعه هم آمده است.

لایه‌ای پایین‌تر از سینتکس interface

اینجا وارد لایه‌ای می‌شوم که در پروژه‌های معمولی به آن نگاه نمی‌شود اما برای مهندسان پلتفرم و توسعه‌دهنده‌های ارشد اهمیت دارد. آنچه سیستم تایپ TS با interfaceهای شما می‌کند، در پنج مفهوم خلاصه می‌شود:

  1. Structural Typing و ماهیت غیراسمی interface: interface در TS نام‌محور (nominal) نیست؛ ساختاری (structural) است. یعنی یک شیء اگر ساختار یک interface را داشته باشد، از نظر TS همان تایپ است، حتی اگر در جای دیگری از کد به‌طور صریح به آن interface اشاره نکرده باشد. این ویژگی انعطاف فوق‌العاده‌ای می‌دهد اما پیامد جالبی هم دارد: در بعضی موارد، دو interface با ساختار مشابه و نام متفاوت، به‌عنوان یک تایپ پذیرفته می‌شوند. اگر می‌خواهید تمایز اسمی ایجاد کنید (مثلاً UserId نباید با ProductId جابه‌جا شود)، باید از یک trick مثل branding استفاده کنید. مطالعه‌ی موازی این لایه در تفاوت تایپ اسکریپت و جاوااسکریپت آمده است.
  2. Declaration Merging و ترتیب کامپایل: وقتی TS با چند interface هم‌نام روبرو می‌شود، آن‌ها را ادغام می‌کند — اما با یک شرط: نوع خاصیت‌های تعارض‌دار باید یکسان باشد. اگر در یک interface id: number و در دیگری id: string باشد، خطای کامپایل رخ می‌دهد. این رفتار در پروژه‌های بزرگ که از مدل‌های plugin استفاده می‌کنند، یک نقطه‌ی حساس است. مطالعه‌ی موازی در اینترفیس در تایپ اسکریپت آمده است.
  3. Interface Erasure و هزینه‌ی صفر در Runtime: interfaceها در زمان کامپایل به‌طور کامل حذف می‌شوند. یعنی هیچ حجم اضافه در JS نهایی وجود ندارد. این ویژگی، interface را به گزینه‌ای کاملاً «رایگان» در بُعد کارایی تبدیل می‌کند. اما نکته‌ی ظریف: اگر یک interface را به‌عنوان Type Guard استفاده می‌کنید، این محافظت را در runtime باید با کد اضافه پیاده‌سازی کنید چون TS در runtime هیچ محافظتی نمی‌کند. مطالعه‌ی موازی این لایه در بهینه سازی جاوااسکریپت آمده است.
  4. Interface و Interaction با Module System: interfaceها در TS، بین ماژول‌ها با import و export type انتقال پیدا می‌کنند. تفاوت import و import type در TS مدرن اهمیت دارد: با import type، مطمئن می‌شوید که کامپایلر آن import را حذف می‌کند و این تضمین می‌کند که حتی اگر ماژول مبدأ به‌طور کامل tree-shake شود، خطای runtime رخ نمی‌دهد. مطالعه‌ی موازی در ماژول ها در تایپ اسکریپت.
  5. Interface و کتابخانه‌های عمومی: اگر شما کتابخانه‌ای می‌نویسید که کاربران TS دارد، استفاده از interface به‌جای type یک مزیت بزرگ است: کاربران می‌توانند با Declaration Merging، تایپ‌های شما را گسترش دهند. مثلاً کتابخانه‌ای که یک interface پایه به اسم User دارد، به کاربر اجازه می‌دهد با اضافه کردن یک interface در پروژه‌ی خودش، فیلدهای بیشتری به آن اضافه کند. این الگو در کتابخانه‌هایی مثل Passport.js و Express بسیار استفاده می‌شود. مطالعه‌ی موازی در تایپ اسکریپت با Node.js و تایپ اسکریپت با React آمده است.

یک تجربه‌ی واقعی از پروژه‌ای که با مسئله‌ی Structural Typing روبرو شدیم: در یک سیستم پرداخت، دو interface CustomerId و TransactionId تعریف کرده بودیم که هر دو string بودند. به‌دلیل structural typing، TS هر جا CustomerId را با TransactionId جابه‌جا می‌کرد، خطا نمی‌گرفت. بعد از یک باگ در production که در آن به‌جای شناسه‌ی مشتری، شناسه‌ی تراکنش به API ارسال شده بود، از تکنیک branding استفاده کردیم:

type CustomerId = string & { readonly __brand: "CustomerId" };
type TransactionId = string & { readonly __brand: "TransactionId" };

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

اگر روی پروژه‌های وردپرسی هستید و می‌خواهید این لایه‌ها را در development pipeline خود اعمال کنید، پیشنهاد می‌کنم ابتدا به توسعه وردپرس از صفر نگاهی بیندازید. برای مطالعه‌ی موازی با استانداردها و معماری، استانداردهای HTML و CSS و CSS مدرن از Flexbox تا Grid دید وسیع‌تری می‌دهند. برای درک این لایه در چارچوب کارایی، بهینه سازی جاوااسکریپت و بهینه‌سازی سرعت سایت منابع کلیدی هستند. اگر روی فریم‌ورک‌ها متمرکز هستید، تایپ اسکریپت با React و تایپ اسکریپت با Node.js را هم ببینید.

interface در TypeScript، تنها یک ساختار تایپ نیست؛ یک قرارداد معنایی است که در لایه‌ی انسانی و معماری هم اثر می‌گذارد. تفاوت بین یک پروژه‌ی TypeScript معمولی و یک پروژه‌ی حرفه‌ای، دقیقاً در طراحی همین قراردادهاست.

سخن آخر این قرارداد

interface در TypeScript را می‌توان در یک جمله خلاصه کرد: «ابزاری برای تعریف قراردادهای ساختاری که در سه لایه — تایپ، معماری و ارتباط انسانی — اثر می‌گذارد.» سه درس که از این مسیر با خودم بردم:

  1. interface را به‌عنوان قرارداد ببینید، نه برچسب. اگر interfaceهای شما فقط «شکل» را توصیف می‌کنند و به معنا و مرزهای دامنه توجهی ندارند، در پروژه‌های بزرگ به یک بدهی فنی تبدیل می‌شوند. قبل از نوشتن interface، بپرسید این تایپ نماینده‌ی چه مفهوم کسب‌وکاری است.
  2. یک قاعده‌ی مشخص در تیم داشته باشید. یا interface برای اشیاء و type برای بقیه، یا interface برای تایپ‌های عمومی و type برای داخلی. قاعده را در مستندات پروژه بنویسید و در بازبینی‌ها اجرا کنید. این یک تصمیم کوچک است که در طول عمر پروژه، صدها ساعت بحث را حذف می‌کند.
  3. ابزارهای عمیق‌تر را جدی بگیرید. Declaration Merging برای گسترش کتابخانه‌های خارجی، Extends برای نهادهای دامنه، و Branding برای تمایز تایپ‌های هم‌شکل. این سه الگو، تفاوت بین یک پروژه‌ی TS معمولی و یک پروژه‌ی TS حرفه‌ای را می‌سازند.

مسیر یادگیری فرانت‌اند با این نوشته تمام نمی‌شود. اگر می‌خواهید مرحله‌ی بعدی را بردارید، آموزش تایپ اسکریپت از صفر، تایپ ها در تایپ اسکریپت و Generics در تایپ اسکریپت سه قدم منطقی بعدی هستند. اگر روی فریم‌ورک‌ها متمرکز هستید، تایپ اسکریپت با React، تایپ اسکریپت با Node.js و ماژول ها در تایپ اسکریپت دید وسیع‌تری می‌دهند. اگر هم به سمت خطاها و دیباگ می‌روید، خطاهای رایج تایپ اسکریپت و تنظیمات tsconfig منابع کلیدی هستند.

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