درود دوستان! اگه با تایپ‌اسکریپت کار کرده باشین می‌دونین که برای یک کار راه حل‌های زیادی وجود داره که این موضوع بعضی وقت‌ها تصمیم‌گیری رو برای انتخاب یک روش درست سخت می‌کنه. یکی از این دو راهی‌ها، استفاده از تایپ یا اینترفیس هست که توی این قسمت می‌خوایم تفاوت اونها رو بررسی کنیم.

 

نکته

هدف این قسمت بررسی تفاوت‌های این دو ویژگی هست و هرچند به صورت مختصر با این دو ویژگی آشنا می‌شیم، فرض می‌کنیم که با فلسفه و کاربرد اینترفیس‌ها آشنایی دارین.

 

تایپ (Type Alias) چیه؟

همونطور که می‌دونیم توی جاوااسکریپت/تایپ‌اسکریپت ما تایپ‌های مختلفی مثل number و string داریم. اینها تایپ‌های درونی این زبان هستن. اما با یک ویژگی اختصاصی تایپ‌اسکریپت به اسم Type Alias می‌تونیم تایپ‌هایی با اسم دلخواه بسازیم. نحوهٔ ساختن یک تایپ دلخواه با Type Alias به صورت زیر هست:

type ID = number;
type Name = string;

توی کد بالا ما دو تا تایپ ساختیم. برای مثال اگه توی برنامه‌مون برای اسم‌ها از تایپ Name استفاده کنیم، معنا و خوانایی بیشتری داره نسبت به تایپ string:

function sendMessage(user: Name) {

}

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

type Person = {
  name: string;
  age: number;
}

ما اینجا یک تایپ اختصاصی ساختیم به اسم Person که به صورت زیر می‌تونه به متغیرها، نوع پارامتر یا خروجی توابع نسبت داده بشه:

function updateUser(person: <<Person>>): <<Person>> {
  const newPerson = {...person};
  newPerson.age++;

  return newPerson;
}

const mario: <<Person>> = {
  name: "Mario",
  age: 4,
};

updateUser(mario);

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

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

نحوهٔ استفاده از این اینترفیس هیچ تفاوتی با تایپی که ساختیم نداره و می‌تونه توی مثال بالا استفاده بشه. در واقع توی خیلی از جاها این دو ویژگی می‌تونن به همین شکل بجای همدیگه استفاده بشن. پس چه تفاوت‌هایی وجود داره؟

 

تفاوت اول: کار با مقادیر Primitive

این یکی از مهمترین تفاوت‌های این دو ویژگی هست. تو مثال‌های بالا دیدیم که ما تونستیم برای تایپ string یک تایپ با اسم دلخواه خودمون بسازیم. همونطور که می‌دونیم string یک Primitive (مقدار غیر آبجکتی) هست و ما برای کار با مقادیر Primitive نمی‌تونیم از اینترفیس‌ها استفاده کنیم. در واقع ما از اینترفیس‌ها فقط می‌تونیم برای شکل دادن به آبجکت‌ها استفاده کنیم. اما با Type Alias می‌تونیم هم برای مقادیر آبجکتی و هم Primitive ها تایپ دلخواه بسازیم:

interface Person {
  name: string;
}

// or
type Person {
  person: string;
}

type Name = string;

 

تفاوت دوم: Merge کردن اینترفیس‌ها و Type Aliases

یک تفاوت مهم دیگه (شاید مهمترین تفاوت) این دو ویژگی اینه که ما توی یک برنامه می‌تونیم چند اینترفیس با اسم‌های یکسان داشته باشیم. اما این کار برای تایپ‌ها شدنی نیست و ما خطا می‌گیریم:

interface Person {
  name: string;
}

interface Person {
  age: number;
}

const john: Person = {
  name: "John",
  age: 3,
};

توی کد بالا دو اینترفیس اول با هم ترکیب (Merge) شدن و یک اینترفیس شامل پراپرتی‌های age و name رو تشکیل دادن. اما داشتن دو تایپ همنام توی برنامه شدنی نیست:

type Person = {
  name: string;
}

type Person = {
  age: number;
}

// Error: Duplicate identifier 'Person'

اگه مشغول توسعهٔ کتابخونه‌‌ای هستیم و اگه این کتابخونه اینترفیس‌هایی رو جهت استفاده توی برنامه‌های دیگه ارائه میده، معمولاً توصیه میشه که اون اینترفیس رو با Interface بنوسیم و Export کنیم تا بقیه توسعه‌دهنده‌ها بتونن برنامه رو به شکل دلخواه توسعه بدن.

 

تفاوت سوم: قابلیت توسعه‌پذیری (Extend شدن)

هم اینترفیس‌ها و هم تایپ‌ها می‌تونن Extend بشن. تفاوت توی نحوهٔ پیاده‌سازی هست. برای توسعه‌دادن یک اینترفیس از کلمه‌کلیدی extends استفاده می‌کنیم:

interface Person {
  name: string;
}

interface Employee extends Person {
  age: number;  
}

برای توسعه‌دادن تایپ‌ها باید با استفاده علامت & عملیات Intersection انجام بدیم:

type Person = {
  name: string;
}

type Employee = Person <<&>> { age: number }

 

تفاوت چهارم: قابلیت Implement شدن توسط کلاس‌ها

هر دو می‌تونن توسط کلاس‌ها Implement بشن:

interface Workable {
  work(): void;
}

type Person = {
  rest(): void;
}

class Employee implements Person, Workable {
  rest() { }
  work() { }
}

تفاوتی که اینجا وجود داره اینه که اگه یک تایپ با یک تایپ دیگه Union شده باشه (با استفاده از علامت |) این تایپ نمی‌تونه توسط کلاس‌ها implement بشه و خطا می‌گیریم:

type WorkOrRest = { work(): void } <<|>> { rest(): void }

class Employee implements WorkOrRest {
  rest() { }
}

class Employer implements WorkOrRest {
  work() { }
}

// Error: A class can only implement an object type or intersection of object types with statically known members

این خطا به این دلیله که تایپ‌اسکریپت (یا کلا هر زبان شی‌گرایی) از یک کلاس انتظار داره که اعضای قابل پیش‌بینی‌ای داشته باشه. توی کد بالا می‌بینیم که دو کلاس دارن WorkOrRest رو پیاده‌سازی می‌کنن، اما اعضای این کلاس‌ها قابل پیشبینی نیست:

function func(user: WorkOrRest) {
  user.rest();
  // Error: rest is not a function
}

پس منطقیه که تایپ‌اسکریپت اجازهٔ چنین کاری رو به ما نده.

 

 

 

از کدوم استفاده کنیم؟ 🤔

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

خب دوستان این قسمت هم به پایان رسید. امیدوارم استفاده کرده باشید. روزتون خوش 😉✌️

 

https://medium.com/@martin_hotell/interface-vs-type-alias-in-typescript-2-7-2a8f1777af4c

https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces

https://stackoverflow.com/questions/37233735/interfaces-vs-types-in-typescript

https://bobbyhadz.com/blog/typescript-interface-can-only-extend-object-type