اینترفیس‌ها و Type Alias در تایپ اسکریپت: ساختاردهی بهتر کد

فهرست مطالب

اینترفیس‌ها و Type Alias در تایپ‌اسکریپت: ساختاردهی بهتر کد

در دنیای توسعه نرم‌افزار مدرن، پیچیدگی سیستم‌ها به طور فزاینده‌ای در حال افزایش است. این پیچیدگی نه تنها از منظر منطق کسب‌وکار، بلکه از جهت مدیریت داده‌ها و انواع آن‌ها نیز خود را نشان می‌دهد. تایپ‌اسکریپت (TypeScript) به عنوان یک سوپراست از جاوااسکریپت، ابزارهای قدرتمندی را برای مدیریت این پیچیدگی‌ها و بهبود کیفیت کد ارائه می‌دهد. از جمله مهم‌ترین این ابزارها، اینترفیس‌ها (Interfaces) و Type Alias (نام‌های مستعار برای انواع) هستند. این دو مفهوم، سنگ بنای یک معماری کد قوی و قابل نگهداری در پروژه‌های تایپ‌اسکریپت محسوب می‌شوند و به توسعه‌دهندگان کمک می‌کنند تا قراردادهای داده‌ای صریح و واضحی را تعریف کنند.

هدف اصلی تایپ‌اسکریپت، افزودن سیستم نوع (Type System) به جاوااسکریپت است. این سیستم نوع، قبل از اجرای کد (در زمان کامپایل)، خطاهای مربوط به نوع را شناسایی می‌کند. این رویکرد به ویژه در پروژه‌های بزرگ با تیم‌های توسعه‌دهنده متعدد، بسیار مفید است، زیرا به کاهش باگ‌ها، بهبود خوانایی کد و افزایش سرعت توسعه کمک شایانی می‌کند. اینترفیس‌ها و Type Alias ابزارهایی هستند که این امکان را فراهم می‌آورند تا ساختار داده‌ها، پارامترهای توابع و حتی کلاس‌ها را به گونه‌ای دقیق و مستند تعریف کنیم که هم ابهام را از بین ببرد و هم از خطاهای ناشی از ناسازگاری انواع جلوگیری کند.

در این مقاله جامع و تخصصی، ما به بررسی عمیق این دو مفهوم کلیدی در تایپ‌اسکریپت خواهیم پرداخت. ابتدا هر یک را به صورت جداگانه تعریف کرده، سپس به قابلیت‌های پیشرفته‌تر و کاربردهای متنوع آن‌ها می‌پردازیم. در نهایت، با مقایسه دقیق تفاوت‌ها و شباهت‌هایشان، راهنمایی‌هایی برای انتخاب بهترین ابزار در سناریوهای مختلف ارائه خواهیم داد. با مطالعه این مقاله، نه تنها درک عمیق‌تری از اینترفیس‌ها و Type Alias پیدا خواهید کرد، بلکه قادر خواهید بود با استفاده از آن‌ها، کدی با ساختار بهتر، خوانایی بیشتر و پایداری بالاتر بنویسید.

۱. اینترفیس‌ها در تایپ‌اسکریپت: تعاریف و کاربردها

اینترفیس (Interface) در تایپ‌اسکریپت، یک مفهوم قدرتمند برای تعریف “قرارداد” یا “شکل” (shape) اشیاء است. به عبارت ساده‌تر، یک اینترفیس مشخص می‌کند که یک شیء باید چه ویژگی‌ها (properties) و متدهایی داشته باشد تا با آن اینترفیس سازگار باشد. اینترفیس‌ها تنها در زمان کامپایل تایپ‌اسکریپت وجود دارند و پس از کامپایل به جاوااسکریپت ساده، از بین می‌روند. این بدان معناست که آن‌ها هیچ سربار اجرایی (runtime overhead) ندارند و فقط برای بررسی نوع (type checking) در زمان توسعه استفاده می‌شوند.

تعریف و سینتکس پایه

یک اینترفیس با کلمه کلیدی interface تعریف می‌شود و سپس نام اینترفیس و بلاکی شامل تعریف ویژگی‌ها و متدها می‌آید. هر ویژگی دارای یک نام و یک نوع است.


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

// استفاده از اینترفیس
const user1: User = {
  id: 1,
  name: "علی",
  email: "ali@example.com",
};

// اگر یکی از ویژگی‌ها حذف شود یا نوع آن اشتباه باشد، تایپ‌اسکریپت خطا می‌دهد.
/*
const user2: User = {
  id: 2,
  name: "مریم",
  // خطا: ویژگی 'email' در نوع '{ id: number; name: string; }' موجود نیست.
};
*/

موارد استفاده رایج اینترفیس‌ها

اینترفیس‌ها در سناریوهای متعددی برای بهبود ساختار و خوانایی کد مورد استفاده قرار می‌گیرند:

  1. تعریف شکل اشیاء (Object Shapes): رایج‌ترین کاربرد اینترفیس‌ها، تعریف ساختار دقیق اشیائی است که در برنامه شما استفاده می‌شوند. این کار به اطمینان از سازگاری داده‌ها در سراسر برنامه کمک می‌کند.
  2. 
      interface Product {
        id: number;
        name: string;
        price: number;
        description?: string; // ویژگی اختیاری
        readonly createdAt: Date; // ویژگی فقط خواندنی
      }
    
      const laptop: Product = {
        id: 101,
        name: "لپ تاپ HP",
        price: 12000000,
        createdAt: new Date(),
      };
    
      // laptop.createdAt = new Date(); // خطا: ویژگی فقط خواندنی است.
      
  3. قراردادهای توابع (Function Contracts): شما می‌توانید از اینترفیس‌ها برای تعریف شکل توابع نیز استفاده کنید. این کار به ویژه برای توابعی که به عنوان پارامتر به توابع دیگر ارسال می‌شوند (callback functions) مفید است.
  4. 
      interface MathOperation {
        (x: number, y: number): number;
      }
    
      const add: MathOperation = (a, b) => a + b;
      const subtract: MathOperation = (a, b) => a - b;
    
      console.log(add(5, 3));      // 8
      console.log(subtract(10, 4)); // 6
    
      // اگر تابع با امضای تعریف شده در اینترفیس مطابقت نداشته باشد، خطا می‌دهد.
      /*
      const multiply: MathOperation = (a, b, c) => a * b * c; // خطا: تعداد آرگومان‌ها نامعتبر است.
      */
      
  5. پیاده‌سازی در کلاس‌ها (Implementing Classes): کلاس‌ها می‌توانند یک یا چند اینترفیس را پیاده‌سازی (implement) کنند. این بدان معناست که کلاس متعهد می‌شود که تمامی ویژگی‌ها و متدهای تعریف شده در آن اینترفیس را داشته باشد. این یک روش عالی برای ایجاد قراردادهای قوی بین کلاس‌ها و اطمینان از سازگاری آن‌ها است.
  6. 
      interface Logger {
        log(message: string): void;
        error(message: string): void;
      }
    
      class ConsoleLogger implements Logger {
        log(message: string): void {
          console.log(`[INFO]: ${message}`);
        }
    
        error(message: string): void {
          console.error(`[ERROR]: ${message}`);
        }
      }
    
      class FileLogger implements Logger {
        log(message: string): void {
          // منطق ذخیره در فایل
          console.log(`[FILE LOG - INFO]: ${message}`);
        }
    
        error(message: string): void {
          // منطق ذخیره خطا در فایل
          console.error(`[FILE LOG - ERROR]: ${message}`);
        }
      }
    
      const logger1: Logger = new ConsoleLogger();
      logger1.log("این یک پیام از ConsoleLogger است.");
    
      const logger2: Logger = new FileLogger();
      logger2.error("خطایی از FileLogger رخ داده است.");
      

۲. قابلیت‌های پیشرفته اینترفیس‌ها

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

ویژگی‌های اختیاری (Optional Properties)

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


interface Car {
  make: string;
  model: string;
  year?: number; // سال ساخت اختیاری است
}

const myCar: Car = {
  make: "Toyota",
  model: "Camry"
};

const yourCar: Car = {
  make: "Honda",
  model: "Civic",
  year: 2020
};

ویژگی‌های فقط خواندنی (Readonly Properties)

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


interface Point {
  readonly x: number;
  readonly y: number;
}

let p1: Point = { x: 10, y: 20 };
// p1.x = 5; // خطا! 'x' یک ویژگی فقط خواندنی است.

امضای ایندکس (Index Signatures)

اگر از قبل نمی‌دانید که یک شیء چه تعداد یا چه نام‌هایی از ویژگی‌ها را خواهد داشت، اما می‌دانید که نوع مقادیر آن ویژگی‌ها چیست، می‌توانید از امضای ایندکس استفاده کنید. این برای اشیائی مانند دیکشنری‌ها (dictionaries) یا آرایه‌های انجمنی (associative arrays) مفید است.


interface StringDictionary {
  [key: string]: string; // هر کلیدی از نوع string باشد و مقدار آن هم string باشد.
}

const myDictionary: StringDictionary = {
  name: "علی",
  city: "تهران",
  job: "برنامه نویس"
};

// const invalidDictionary: StringDictionary = {
//   age: 30 // خطا! مقدار باید از نوع string باشد.
// };

interface NumberArray {
    [index: number]: string; // یک آرایه که اندیس‌های عددی دارد و مقادیرش string هستند.
}

const myArray: NumberArray = ["apple", "banana", "orange"];
console.log(myArray[0]); // "apple"

گسترش اینترفیس‌ها (Extending Interfaces)

اینترفیس‌ها می‌توانند یک یا چند اینترفیس دیگر را گسترش (extend) دهند. این به معنای وراثت (inheritance) است؛ اینترفیس فرزند تمامی ویژگی‌ها و متدهای اینترفیس والد را به ارث می‌برد و می‌تواند ویژگی‌های جدیدی نیز اضافه کند.


interface Animal {
  name: string;
  age: number;
}

interface Dog extends Animal {
  breed: string;
  bark(): void;
}

const myDog: Dog = {
  name: "Buddy",
  age: 5,
  breed: "Golden Retriever",
  bark: () => console.log("Woof!")
};

myDog.bark(); // Woof!

ادغام اعلان (Declaration Merging)

یکی از ویژگی‌های منحصر به فرد اینترفیس‌ها در تایپ‌اسکریپت، قابلیت “ادغام اعلان” است. اگر چندین اینترفیس با یک نام یکسان در اسکوپ (scope) تعریف شوند، تایپ‌اسکریپت آن‌ها را به صورت خودکار با هم ادغام می‌کند. این قابلیت به ویژه در کتابخانه‌ها و فایل‌های تعریف نوع (.d.ts) مفید است.


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

interface User {
  email: string;
  phone?: string;
}

// اینترفیس User نهایی به صورت زیر خواهد بود:
/*
interface User {
  id: number;
  name: string;
  email: string;
  phone?: string;
}
*/

const newUser: User = {
  id: 1,
  name: "رضا",
  email: "reza@example.com"
};

console.log(newUser.id);
console.log(newUser.email);

۳. Type Alias در تایپ‌اسکریپت: تعاریف و کاربردها

Type Alias (نام مستعار برای نوع) راهی برای دادن یک نام جدید به هر نوع موجود است. این نام‌گذاری می‌تواند برای انواع اولیه (primitives)، انواع ترکیبی (union types, intersection types)، توابع، تاپل‌ها و حتی انواع پیچیده استفاده شود. برخلاف اینترفیس‌ها که عمدتاً برای تعریف شکل اشیاء استفاده می‌شوند، Type Alias بسیار انعطاف‌پذیرتر بوده و می‌تواند برای نام‌گذاری هر نوعی به کار رود.

تعریف و سینتکس پایه

یک Type Alias با کلمه کلیدی type تعریف می‌شود و سپس نام مستعار و علامت مساوی و در نهایت نوع مورد نظر می‌آید.


type UserID = string | number; // یک UserID می‌تواند string یا number باشد

function displayUserID(id: UserID): void {
  console.log(`شناسه کاربری: ${id}`);
}

displayUserID(12345);      // شناسه کاربری: 12345
displayUserID("ABC-XYZ"); // شناسه کاربری: ABC-XYZ

// displayUserID(true); // خطا: 'boolean' به 'UserID' قابل انتساب نیست.

موارد استفاده رایج Type Alias

انعطاف‌پذیری Type Alias آن را برای طیف وسیعی از سناریوها مناسب می‌سازد:

  1. نام‌گذاری انواع اولیه و ترکیبی: می‌توان برای انواع ساده (مثل string، number، boolean) یا ترکیبی (مثل Union Types یا Intersection Types) نام‌های با معنی‌تر ایجاد کرد.
  2. 
      type Age = number;
      type FullName = string;
      type IsActive = boolean;
    
      type UserStatus = "online" | "offline" | "away"; // Literal Union Type
    
      const myAge: Age = 30;
      const status: UserStatus = "online";
      
  3. تعریف توابع: می‌توان یک امضای تابع (function signature) را به عنوان یک نوع تعریف کرد.
  4. 
      type CallbackFunction = (data: string) => void;
    
      function processData(text: string, callback: CallbackFunction): void {
        const processedText = text.toUpperCase();
        callback(processedText);
      }
    
      processData("hello typescript", (result) => {
        console.log(`نتیجه پردازش: ${result}`); // نتیجه پردازش: HELLO TYPESCRIPT
      });
      
  5. تعریف تاپل‌ها (Tuples): تاپل‌ها آرایه‌هایی با تعداد و نوع ثابت از عناصر هستند. Type Alias برای نام‌گذاری آن‌ها بسیار مناسب است.
  6. 
      type RGB = [number, number, number]; // تاپل سه عددی برای رنگ RGB
    
      const red: RGB = [255, 0, 0];
      const green: RGB = [0, 255, 0];
    
      // const invalidColor: RGB = [255, 0]; // خطا: تعداد عناصر نامعتبر است.
      
  7. انواع پیچیده و بازگشتی (Recursive Types): Type Alias می‌تواند برای تعریف انواع بازگشتی (انواعی که به خودشان ارجاع می‌دهند) استفاده شود. این قابلیت در تعریف ساختارهای درختی یا لیستی مفید است.
  8. 
      type TreeNode = {
        value: string;
        children?: TreeNode[]; // یک گره ممکن است آرایه‌ای از گره‌های دیگر را داشته باشد
      };
    
      const folderStructure: TreeNode = {
        value: "Root",
        children: [
          {
            value: "Documents",
            children: [
              { value: "Report.pdf" },
              { value: "Notes.txt" }
            ]
          },
          {
            value: "Images",
            children: [
              { value: "Photo1.jpg" }
            ]
          }
        ]
      };
    
      console.log(folderStructure.children?.[0].children?.[0].value); // Report.pdf
      

۴. Type Alias و قابلیت‌های فراتر

همانطور که اشاره شد، Type Alias انعطاف‌پذیری بالایی دارد و می‌تواند برای ایجاد انواع پیچیده و قدرتمند استفاده شود.

انواع اتحادیه (Union Types)

با استفاده از عملگر |، می‌توانید یک Type Alias تعریف کنید که نماینده چندین نوع مختلف باشد. این یعنی یک متغیر از این نوع می‌تواند یکی از انواع تعریف شده باشد.


type Status = "success" | "error" | "pending"; // یک مقدار متنی محدود به این سه حالت

function showStatus(currentStatus: Status): void {
  console.log(`وضعیت کنونی: ${currentStatus}`);
}

showStatus("success");
// showStatus("failed"); // خطا: "failed" به 'Status' قابل انتساب نیست.

type FlexibleID = string | number | boolean; // شناسه می‌تواند رشته، عدد یا بولین باشد

let someId: FlexibleID;
someId = 123;
someId = "abc";
someId = true;
// someId = null; // خطا: 'null' به 'FlexibleID' قابل انتساب نیست.

انواع تقاطع (Intersection Types)

با استفاده از عملگر &، می‌توانید چندین نوع را با هم ترکیب کنید تا یک نوع جدید بسازید که تمامی ویژگی‌های انواع ترکیبی را دارا باشد. این به معنای “A و B” است.


interface HasName {
  name: string;
}

interface HasAge {
  age: number;
}

type Person = HasName & HasAge; // یک Person هم name دارد و هم age

const person1: Person = {
  name: "سارا",
  age: 25
};

interface Flyable {
  fly(): void;
}

interface Swimmable {
  swim(): void;
}

type FlyingFish = Flyable & Swimmable; // ماهی پرنده هم پرواز می‌کند و هم شنا

const myFlyingFish: FlyingFish = {
  fly: () => console.log("Flying through air!"),
  swim: () => console.log("Swimming in water!")
};

myFlyingFish.fly();
myFlyingFish.swim();

انواع لغوی (Literal Types)

Literal Types به شما این امکان را می‌دهند که انواع را به مقادیر خاصی محدود کنید. این مقادیر می‌توانند رشته‌ها، اعداد یا مقادیر بولین باشند. این مفهوم اغلب با Union Types ترکیب می‌شود تا مجموعه‌ای از مقادیر مجاز را تعریف کند.


type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

function makeRequest(url: string, method: HttpMethod): void {
  console.log(`درخواست ${method} به آدرس ${url}`);
}

makeRequest("/api/users", "GET");
// makeRequest("/api/products", "PATCH"); // خطا: "PATCH" به 'HttpMethod' قابل انتساب نیست.

type Coin = 1 | 5 | 10 | 25 | 50 | 100; // انواع سکه‌ها بر حسب ریال (مثال)

const myCoin: Coin = 50;
// const invalidCoin: Coin = 75; // خطا: 75 به 'Coin' قابل انتساب نیست.

انواع ابزاری (Utility Types) با Type Alias

تایپ‌اسکریپت مجموعه‌ای از انواع ابزاری توکار (Built-in Utility Types) را ارائه می‌دهد که برای انجام عملیات رایج بر روی انواع استفاده می‌شوند. اینها خودشان Type Alias هستند و می‌توانند با Type Aliasهای سفارشی شما نیز ترکیب شوند تا انواع پیچیده‌تری را بسازند.

  • Partial: همه ویژگی‌های T را اختیاری می‌کند.
  • Required: همه ویژگی‌های T را اجباری می‌کند.
  • Readonly: همه ویژگی‌های T را فقط خواندنی می‌کند.
  • Pick: یک نوع جدید ایجاد می‌کند که فقط شامل ویژگی‌های K از نوع T است.
  • Omit: یک نوع جدید ایجاد می‌کند که تمامی ویژگی‌های T به جز K را شامل می‌شود.

interface UserProfile {
  id: number;
  name: string;
  email: string;
  age?: number;
}

type PartialUserProfile = Partial;
// { id?: number; name?: string; email?: string; age?: number; }

const userUpdate: PartialUserProfile = {
  name: "رضا",
  age: 35
};

type UserContactInfo = Pick; // فرض کنید phone را اضافه کرده‌ایم
// { email: string; phone?: string; }
// اگر phone در UserProfile نباشد، خطا می‌دهد (در اینجا برای UserProfile مثال بالا، phone نیست)

type UserWithoutIdAndEmail = Omit;
// { name: string; age?: number; }

انواع جنریک (Generic Type Aliases)

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


type Pair = [T, U]; // یک تاپل دو عضوی با انواع دلخواه

const stringNumberPair: Pair = ["Hello", 123];
const booleanArrayPair: Pair = [true, ["apple", "banana"]];

type Result = { success: true; data: T } | { success: false; error: E };

function fetchData(): Result {
  const isSuccess = Math.random() > 0.5;
  if (isSuccess) {
    return { success: true, data: ["Item1", "Item2"] };
  } else {
    return { success: false, error: "خطا در بارگذاری داده‌ها" };
  }
}

const dataResult = fetchData();
if (dataResult.success) {
  console.log("داده‌ها: ", dataResult.data);
} else {
  console.error("خطا: ", dataResult.error);
}

۵. تفاوت‌های کلیدی: اینترفیس در مقابل Type Alias

با وجود شباهت‌های زیاد و همپوشانی در برخی کاربردها، اینترفیس‌ها و Type Alias تفاوت‌های مهمی دارند که درک آن‌ها برای انتخاب صحیح ابزار در هر سناریو حیاتی است.

۱. ادغام اعلان (Declaration Merging)

اینترفیس‌ها: از قابلیت ادغام اعلان (Declaration Merging) پشتیبانی می‌کنند. این بدان معناست که اگر چندین اعلان اینترفیس با یک نام در اسکوپ وجود داشته باشد، تایپ‌اسکریپت آن‌ها را به صورت خودکار در یک اینترفیس واحد ادغام می‌کند. این ویژگی برای افزودن تدریجی ویژگی‌ها به یک اینترفیس از ماژول‌های مختلف یا برای گسترش تعریف نوع از کتابخانه‌های شخص ثالث بسیار مفید است.


// در فایل lib.d.ts یا یک کتابخانه
interface User {
  id: number;
  name: string;
}

// در کد شما
interface User {
  email: string;
}

// نتیجه نهایی User: { id: number; name: string; email: string; }

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


// type UserAlias = { id: number; name: string; };
// type UserAlias = { email: string; }; // خطا: 'UserAlias' از قبل تعریف شده است.

۲. گسترش (Extending) و پیاده‌سازی (Implementing)

اینترفیس‌ها: می‌توانند توسط اینترفیس‌های دیگر (با کلمه کلیدی extends) و توسط کلاس‌ها (با کلمه کلیدی implements) گسترش یا پیاده‌سازی شوند. این قابلیت به آن‌ها امکان می‌دهد که به عنوان قراردادهایی برای اشیاء و کلاس‌ها عمل کنند و بخشی از سلسله مراتب وراثت شیءگرا باشند.


interface Base { prop1: string; }
interface Derived extends Base { prop2: number; } // گسترش اینترفیس

class MyClass implements Derived { // پیاده‌سازی اینترفیس
  prop1: string = "hello";
  prop2: number = 123;
}

Type Alias: نمی‌توانند با کلمات کلیدی extends یا implements به صورت مستقیم گسترش یا پیاده‌سازی شوند. برای ترکیب انواع در Type Alias، باید از Intersection Types (با عملگر &) استفاده کنید. کلاس‌ها نیز نمی‌توانند یک Type Alias را مستقیماً implement کنند، مگر اینکه آن Type Alias به یک شکل شیء (object shape) ارجاع دهد.


type BaseType = { prop1: string; };
type DerivedType = BaseType & { prop2: number; }; // ترکیب انواع با Intersection Type

/*
class MyClass implements DerivedType { // این کار در برخی ورژن‌ها ممکن است خطا بدهد یا مفهوم متفاوتی داشته باشد.
                                     // بهتر است کلاس‌ها اینترفیس‌ها را پیاده‌سازی کنند.
  prop1: string = "hello";
  prop2: number = 123;
}
*/

۳. قابلیت نمایش انواع (Representing Other Types)

اینترفیس‌ها: عمدتاً برای تعریف شکل اشیاء استفاده می‌شوند. آن‌ها نمی‌توانند برای نام‌گذاری انواع اولیه (مثل string یا number)، Union Types، Intersection Types، تاپل‌ها یا انواع لغوی (Literal Types) به کار روند.


// interface MyString = string; // خطا
// interface MyUnion = string | number; // خطا

Type Alias: بسیار انعطاف‌پذیرتر هستند و می‌توانند برای نام‌گذاری تقریباً هر نوعی استفاده شوند؛ شامل انواع اولیه، Union Types، Intersection Types، تاپل‌ها، توابع و حتی انواع لغوی.


type MyString = string;
type MyUnion = string | number;
type MyTuple = [string, number];
type MyLiteral = "left" | "right";

۴. پیام‌های خطا (Error Messages)

در برخی موارد، پیام‌های خطای تولید شده توسط تایپ‌اسکریپت ممکن است کمی متفاوت باشد. اینترفیس‌ها گاهی اوقات پیام‌های خطای خواناتری ارائه می‌دهند، به خصوص در هنگام تشخیص عدم تطابق شکل شیء، زیرا نام اینترفیس به وضوح در پیام خطا ذکر می‌شود. در حالی که Type Alias ممکن است نام نوع واقعی را که به آن ارجاع می‌دهد، نشان دهد.

۵. بازگشتی بودن (Recursion)

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


type ListNode = {
  value: number;
  next: ListNode | null;
};

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

۶. چه زمانی از کدام استفاده کنیم؟ – بهترین رویکردها

انتخاب بین اینترفیس و Type Alias اغلب به سناریو و اولویت‌های شما بستگی دارد. در ادامه، یک راهنمای کاربردی برای تصمیم‌گیری ارائه شده است:

قانون کلی (Rule of Thumb)

  • برای تعریف شکل اشیاء (Object Shapes) و قراردادهای کلاس‌ها (Class Contracts)، معمولاً interface را ترجیح دهید. اینترفیس‌ها برای این منظور طراحی شده‌اند و قابلیت‌هایی مانند implements و Declaration Merging آن‌ها را برای این کار ایده‌آل می‌کند.
  • برای تعریف هر نوع دیگری (مانند Union Types، Intersection Types، Literal Types، Tuples، و انواع بازگشتی)، از type استفاده کنید. Type Alias انعطاف‌پذیری لازم را برای این موارد فراهم می‌کند.

سناریوهای استفاده از اینترفیس (interface):

  1. تعریف APIهای عمومی یا قراردادهای کتابخانه‌ای: اگر در حال توسعه یک کتابخانه یا ماژول هستید و می‌خواهید شکل داده‌هایی را که به عنوان ورودی یا خروجی ارائه می‌شوند، مشخص کنید، اینترفیس‌ها انتخاب بهتری هستند. قابلیت Declaration Merging به کاربران اجازه می‌دهد تا در صورت نیاز، ویژگی‌های اضافی به اینترفیس‌های شما اضافه کنند.
  2. تعریف شکل داده‌های پیچیده: برای اشیائی که ساختار مشخصی دارند و قرار است در چندین نقطه از برنامه استفاده شوند.
  3. وقتی کلاس‌ها نیاز به پیاده‌سازی قرارداد خاصی دارند: اگر می‌خواهید مطمئن شوید که یک کلاس، مجموعه‌ای خاص از متدها و ویژگی‌ها را داراست، اینترفیس‌ها انتخاب مناسبی هستند (با استفاده از implements).
  4. گسترش‌پذیری (Extensibility): اگر انتظار دارید که نوع شما در آینده توسط توسعه‌دهندگان دیگر یا حتی توسط خودتان گسترش یابد (مثلاً با اضافه کردن ویژگی‌های جدید در اعلان‌های جداگانه)، اینترفیس‌ها به دلیل Declaration Merging برتری دارند.

سناریوهای استفاده از Type Alias (type):

  1. نام‌گذاری انواع اولیه: برای افزایش خوانایی کد، می‌توانید به انواع ساده مانند string یا number، نام‌های معنی‌دار بدهید (مثلاً type Email = string;).
  2. تعریف Union Types و Intersection Types: این‌ها از نقاط قوت Type Alias هستند. برای مثال، type ID = string | number; یا type AdminUser = User & { role: 'admin' };.
  3. تعریف Literal Types: برای محدود کردن یک متغیر به مجموعه‌ای از مقادیر مشخص (مثلاً type Direction = 'up' | 'down';).
  4. تعریف تاپل‌ها: برای نام‌گذاری ساختارهای آرایه‌ای با تعداد و نوع ثابت از عناصر (مثلاً type Coordinate = [number, number];).
  5. انواع بازگشتی (Recursive Types): برای تعریف ساختارهای داده‌ای که به خودشان ارجاع می‌دهند، مانند درخت‌ها یا لیست‌های پیوندی.
  6. جنریک‌های پیچیده و انواع ابزاری: Type Alias همراه با Utility Types و Generics قدرت زیادی در ایجاد انواع پویا و قابل استفاده مجدد دارد.
  7. سازگاری با جاوااسکریپت: در مواردی که نیاز دارید به شکلی دقیق‌تر، انواع پیچیده جاوااسکریپتی را مدل‌سازی کنید که صرفاً شکل شیء نیستند.

یک نکته مهم: یکپارچگی (Consistency)

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

۷. الگوهای پیشرفته و مثال‌های عملی

برای درک بهتر کاربرد اینترفیس‌ها و Type Alias، بیایید به چند سناریوی واقعی نگاه کنیم:

مدل‌سازی پاسخ‌های API

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


// تعریف ساختار داده برای یک محصول
interface ProductData {
  id: number;
  name: string;
  price: number;
  category: string;
  stock: number;
  description?: string; // اختیاری
  imageUrl: string;
}

// تعریف ساختار پاسخ کلی API برای یک لیست از محصولات با صفحه بندی
interface ApiResponse {
  status: "success" | "error";
  message?: string;
  data: T; // T می‌تواند هر نوعی باشد، مثلاً ProductData[]
  pagination?: {
    totalItems: number;
    currentPage: number;
    itemsPerPage: number;
    totalPages: number;
  };
}

// مثال استفاده:
async function fetchProducts(): Promise> {
  // فرض کنید یک درخواست HTTP به API می‌زنید
  const response = await fetch("/api/products");
  const data = await response.json();
  return data as ApiResponse; // تضمین نوع بازگشتی
}

fetchProducts().then(res => {
  if (res.status === "success") {
    console.log("محصولات:", res.data);
    if (res.pagination) {
      console.log("تعداد کل صفحات:", res.pagination.totalPages);
    }
  } else {
    console.error("خطا:", res.message);
  }
}).catch(err => console.error("خطای شبکه:", err));

در این مثال، ProductData یک اینترفیس برای تعریف شکل یک محصول است. ApiResponse یک اینترفیس جنریک است که ساختار کلی پاسخ API را تعریف می‌کند و از یک Type Parameter T برای داده‌های واقعی استفاده می‌کند. این رویکرد بسیار قدرتمند است زیرا می‌توان از ApiResponse برای انواع مختلفی از داده‌ها (کاربران، سفارشات، و غیره) بدون نیاز به تعریف اینترفیس‌های جداگانه برای هر پاسخ API استفاده کرد.

تعریف تنظیمات (Configuration Objects)

اینترفیس‌ها و Type Alias هر دو می‌توانند برای تعریف ساختار اشیاء تنظیمات استفاده شوند.


// استفاده از اینترفیس
interface AppConfig {
  apiUrl: string;
  port: number;
  debugMode?: boolean;
  database: {
    host: string;
    port: number;
    name: string;
  };
}

const config: AppConfig = {
  apiUrl: "http://localhost:3000/api",
  port: 8080,
  database: {
    host: "localhost",
    port: 5432,
    name: "mydb"
  }
};

// استفاده از Type Alias برای انواع فرعی و پیچیده‌تر
type LogLevel = "info" | "warn" | "error" | "debug";

type FeatureFlags = {
  [key: string]: boolean; // یک شیء با کلیدهای رشته‌ای و مقادیر بولین
};

interface AdvancedAppConfig extends AppConfig {
  logLevel: LogLevel;
  featureFlags: FeatureFlags;
}

const advancedConfig: AdvancedAppConfig = {
  apiUrl: "http://localhost:4000/api",
  port: 9000,
  debugMode: true,
  database: {
    host: "remote-db",
    port: 5432,
    name: "prod_db"
  },
  logLevel: "debug",
  featureFlags: {
    newDashboard: true,
    betaFeatures: false
  }
};

این مثال نشان می‌دهد که چگونه می‌توان اینترفیس‌ها را با یکدیگر گسترش داد و Type Alias را برای تعریف انواع جزئی‌تر مانند LogLevel یا FeatureFlags در کنار آن‌ها به کار برد. این ترکیب، انعطاف‌پذیری و دقت بالایی را در تعریف ساختار تنظیمات فراهم می‌کند.

توابع Higher-Order و کامپوننت‌ها (در فریم‌ورک‌ها)

در فریم‌ورک‌هایی مانند React یا Angular، Type Alias و اینترفیس‌ها برای تعریف انواع Props، State یا آرگومان‌های توابع Higher-Order بسیار کاربردی هستند.


// مثال در React (مفاهیم مشابه در Angular و Vue نیز کاربرد دارد)

// تعریف Props برای یک کامپوننت ساده
interface ButtonProps {
  label: string;
  onClick: () => void;
  variant?: "primary" | "secondary"; // Literal Union Type
  disabled?: boolean;
}

// تعریف نوع برای یک تابع Higher-Order Component (HOC)
type WithLoadingProps = T & { isLoading: boolean };

function Button(props: ButtonProps) {
  return `
    <button class="${props.variant || 'primary'}" ${props.disabled ? 'disabled' : ''} onclick="(${props.onClick.toString()})()">
      ${props.label}
    </button>
  `;
}

// مثال استفاده از HOC (ساختگی)
function withLoading

(Component: (props: P) => string) { return (props: WithLoadingProps

) => { if (props.isLoading) { return `<div>در حال بارگذاری...</div>`; } // باید prop isLoading را از props ارسالی به Component حذف کنیم const { isLoading, ...restProps } = props; return Component(restProps as P); // Type assertion برای سازگاری }; } // ساخت یک دکمه با قابلیت بارگذاری const LoadingButton = withLoading(Button); // const myLoadingButton = LoadingButton({ // label: "ارسال", // onClick: () => console.log("ارسال شد!"), // isLoading: true, // }); // const myRegularButton = Button({ // label: "کلیک کنید", // onClick: () => console.log("کلیک شد!"), // }); // console.log(myLoadingButton); // console.log(myRegularButton);

در این مثال، ButtonProps یک اینترفیس برای تعریف ویژگی‌های (Props) کامپوننت Button است. WithLoadingProps یک Type Alias جنریک است که نشان می‌دهد چگونه می‌توان یک نوع موجود T را با یک ویژگی جدید isLoading ترکیب کرد. این الگوها به شما اجازه می‌دهند تا کدی با قابلیت استفاده مجدد بالا و Type-safe بنویسید، به خصوص در معماری‌های مبتنی بر کامپوننت.

مدل‌سازی گراف‌های داده یا ساختارهای پیچیده

برای ساختارهای داده‌ای مانند گراف‌ها، لیست‌های پیوندی یا ساختارهای سلسله‌مراتبی، Type Alias برای تعریف انواع بازگشتی بسیار مناسب است.


type GraphNode = {
  id: string;
  value: any;
  connections: GraphNode[]; // یک گره به آرایه‌ای از گره‌های دیگر متصل است
};

const nodeA: GraphNode = { id: "A", value: "Start", connections: [] };
const nodeB: GraphNode = { id: "B", value: "Middle", connections: [] };
const nodeC: GraphNode = { id: "C", value: "End", connections: [] };

nodeA.connections.push(nodeB);
nodeB.connections.push(nodeA, nodeC); // مثال گراف دوطرفه
nodeC.connections.push(nodeB);

console.log(nodeA.connections[0].id); // "B"

این مثال نشان می‌دهد که چگونه GraphNode با استفاده از یک Type Alias بازگشتی، می‌تواند ساختار یک گره در یک گراف را تعریف کند که به خود نوع GraphNode ارجاع می‌دهد. این کار باعث می‌شود که شما بتوانید ساختارهای داده پیچیده و خودارجاع را به راحتی مدل‌سازی کنید.

نتیجه‌گیری

اینترفیس‌ها و Type Alias دو ابزار فوق‌العاده قدرتمند و مکمل در تایپ‌اسکریپت هستند که هر یک کاربردها و نقاط قوت خاص خود را دارند. در حالی که اینترفیس‌ها به طور خاص برای تعریف شکل اشیاء و قراردادهای کلاس‌ها با قابلیت‌هایی مانند implements و Declaration Merging بهینه شده‌اند، Type Alias انعطاف‌پذیری بی‌نظیری در نام‌گذاری و ترکیب انواع مختلف (از جمله انواع اولیه، Union Types، Intersection Types، و انواع بازگشتی) ارائه می‌دهد.

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

“تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”

قیمت اصلی 2.290.000 ریال بود.قیمت فعلی 1.590.000 ریال است.

"تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"

"با شرکت در این دوره جامع و کاربردی، به راحتی مهارت‌های برنامه‌نویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر می‌سازد تا به سرعت الگوریتم‌های پیچیده را درک کرده و اپلیکیشن‌های هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفه‌ای و امکان دانلود و تماشای آنلاین."

ویژگی‌های کلیدی:

بدون نیاز به تجربه قبلی برنامه‌نویسی

زیرنویس فارسی با ترجمه حرفه‌ای

۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان