Menu
flag Ar iconالعربيةdown icon

الفرق بين interface و type في TypeScript ومتى تستخدم كلًا منهما

يصف interface وtype كلاهما أشكال الكائنات، وفي أغلب الأحيان يعمل أي منهما. تعرّف على الفروق الحقيقية: دمج التصريحات، وunion وmapped types، وextends مقابل intersection، وindex signatures الضمنية، ورسائل الأخطاء وأداء المترجم، مع قاعدة واضحة للاختيار.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

في وصف شكل الكائن يؤدي interface وtype المهمة نفسها، ويمكن إسناد قيم أحدهما إلى الآخر عندما تتطابق الأشكال. الفروق عند الأطراف: يستطيع type تسمية أشياء لا تستطيعها الواجهة (union وtuples والأنواع المحسوبة)، وتستطيع الواجهة فعل بعض الأشياء التي لا يستطيعها type alias (الدمج، وextends المفحوص).

تقارن TypeScript أنواع الكائنات بحسب البنية، لذلك لا يهم اسم التصريح في قابلية الإسناد. وفيما يلي قائمة المواضع التي يهم فيها الاختيار.

جدول المقارنة

الميزةinterfacetype
أشكال الكائناتنعمنعم
الاختيارية وreadonly والدوال وindex signaturesنعمنعم
الأنواع العامةنعمنعم
Union (A | B)لانعم
tuples والأنواع البدائية وأنواع الدوال وحدهالا (أنواع الدوال كـ call signatures فقط)نعم
mapped types والأنواع الشرطيةلانعم
التوسيعextends، والتعارضات أخطاء&، والتعارضات تصبح never
دمج التصريحاتنعملا (معرّف مكرر)
قابل للإسناد إلى Record<string, T>لانعم، عندما تناسب الخصائص
implements في صنفنعمنعم، إذا كان نوع كائن
التعريفات التعاوديةنعمنعم

ما لا يستطيعه إلا type

كل ما ليس شكل كائن واحد يحتاج إلى type alias:

لا يمكن كتابة أي من هذه بـ interface (الأخيران مشروحان في mapped types والأنواع الشرطية). وهذا هو السبب العملي لاستخدام كل قاعدة كود type في مكان ما، أيًا كان ما تستخدمه لأشكال الكائنات.

ما لا تستطيعه إلا interface: دمج التصريحات

تصريحان interface بالاسم نفسه في النطاق نفسه يندمجان في واحد. أما تصريحان type بالاسم نفسه فهما الخطأ TS2300، Duplicate identifier.

interface Settings {
  theme: string;
}
interface Settings {
  fontSize: number;
}
const s: Settings = { theme: "dark", fontSize: 14 }; // needs both

type Options = { a: number };
type Options = { b: number }; // error TS2300: Duplicate identifier 'Options'.

الدمج هو طريقة توسيع أنواع المكتبات من الخارج: إضافة خاصية إلى Window العام، أو إلى Request في Express، أو إلى نوع السمة في مكتبة ما. إذا كنت تنشر أنواعًا قد يحتاج المستخدمون إلى توسيعها، فاستخدم الواجهات. وفي كود تطبيقك، يحدث الدمج غير المقصود (ملفا سكربت يعلنان اسم الواجهة العامة نفسه) بصمت ما لم يعلن الاثنان الخاصية نفسها بأنواع مختلفة، وهذه إحدى الحجج التي تقدمها بعض الفرق لتفضيل type.

extends مقابل Intersection

توسّع الواجهة بـ extends، ويوسّع type alias بـ &. ينتجان النتيجة نفسها في الغالب، لكنهما يتعاملان مع الخاصية المتعارضة بشكل مختلف. يبلّغ extends عن التعارض عند التصريح:

index.ts(7,11): error TS2430: Interface 'Broken' incorrectly extends interface 'Base'.
  Types of property 'id' are incompatible.
    Type 'number' is not assignable to type 'string'.

أما الـ intersection فيقبل التعارض نفسه بصمت ويحوّل الخاصية إلى never (نص هو رقم في الوقت نفسه). ولا يظهر الخطأ إلا لاحقًا، عندما تحاول إنشاء قيمة:

يشير الخطأ إلى الكائن، بعيدًا عن التصريح الذي تسبب فيه. لبناء أنواع كائنات من أنواع كائنات أخرى، يعطي extends رسالة أفضل.

Index Signatures: فرق يسهل إغفاله

يحصل type alias لنوع كائن على index signature ضمني، لذلك يمكن تمريره حيث يُتوقع Record<string, unknown>. أما الواجهة فلا. هذا مقصود: شرح فريق TypeScript (المسألة #15300 في مستودع TypeScript) هو أن الواجهة يمكن توسيعها بتصريحات لاحقة، فاستنتاج index signature لها أقل أمانًا، وتغيير القاعدة الآن سيكسر كودًا كثيرًا.

الاستدعاء المعلّم بـ @ts-expect-error يعمل رغم ذلك ويطبع حقول Alan: القاعدة وقت الترجمة. وهذا هو السبب المعتاد لخطأ محيّر عندما تُمرَّر قيمة واجهة إلى دالة مساعدة للتسجيل أو التسلسل أو الاستعلام نوع معاملها Record<string, ...>. حوّل ذلك التصريح الواحد إلى type، أو انشر القيمة، أو حدد نوع معامل الدالة المساعدة بواجهة أو بنوع عام بدلًا من ذلك.

الأداء ورسائل الأخطاء

تقترح صفحة الأداء في wiki الخاص بـ TypeScript (قسم «Preferring Interfaces Over Intersections») استخدام interface Foo extends Bar, Baz { ... } بدل type Foo = Bar & Baz & { ... } عند تركيب أنواع الكائنات. وأسبابها: الواجهة نوع كائن واحد مسطح يكتشف تعارضات الخصائص، والعلاقات بين الواجهات تُخزَّن مؤقتًا (أما intersections ككل فلا)، وفحص قيمة مقابل intersection يفحص كل مكوّن قبل النوع المسطح. يهم الفرق في قواعد الكود الكبيرة ذات الأنواع المركبة الكثيرة؛ أما مع بضعة أشكال كائنات عادية فلن تقيسه.

وتشير الصفحة نفسها إلى أن الواجهات تُعرض بشكل أفضل. تُعرض الواجهة باسمها في تلميحات التمرير ورسائل الأخطاء، بينما يُطبع الاسم المستعار لـ intersection غالبًا مفككًا إلى أجزائه، ما يجعل الرسائل الطويلة أصعب قراءة.

أيهما تستخدم

قاعدة تعمل:

  1. أشكال الكائنات: interface. تعطيك extends مفحوصًا، وأخطاء أوضح في التركيبات الكبيرة، وتتيح لمستخدمي المكتبة توسيعها. وهذا يطابق القاعدة العملية في دليل TypeScript: «استخدم interface حتى تحتاج إلى ميزات من type».
  2. كل ما عدا ذلك: type. union وtuples وأنواع الدوال والأنواع الحرفية، وكل ما يُبنى بـ mapped types أو الأنواع الشرطية أو template literal types.
  3. الاستثناء: استخدم type لشكل كائن يجب أن يناسب معاملات Record<string, ...>، أو عندما تريد عمدًا منع الدمج.

استخدام type لكل شيء خيار متسق أيضًا، وتفعله قواعد كود كثيرة. أما الخلط العشوائي بين الاثنين فهو الخيار الوحيد الذي يجب تجنبه: يجعل القراء يتساءلون إن كان الفرق مقصودًا.

الأسئلة الشائعة

ما الفرق بين type و interface في TypeScript؟

كلاهما يصف أشكال الكائنات ويمكن استبدال أحدهما بالآخر لهذا الغرض. يستطيع type أيضًا تسمية union وtuples والأنواع البدائية وmapped types والأنواع الشرطية، وهو ما لا يستطيعه interface. ويدعم interface دمج التصريحات وextends الذي يتحقق من الخصائص المتعارضة. وأنواع الكائنات المكتوبة بـ type يمكن إسنادها أيضًا إلى أنواع index signature مثل Record<string, unknown>، بينما لا يمكن ذلك مع الواجهات.

هل أستخدم type أم interface؟

القاعدة العملية في دليل TypeScript هي: استخدم interface حتى تحتاج إلى ميزة لا يملكها إلا type. عمليًا يعني ذلك الواجهات لأشكال الكائنات وtype لـ union وtuples وأنواع الدوال والأنواع المحسوبة. والفرق التي تستخدم type لكل شيء تنجح أيضًا؛ المهم قاعدة واحدة ثابتة.

هل interface أسرع من type في TypeScript؟

في تركيب أنواع الكائنات، نعم في بعض الحالات. توصي إرشادات الأداء من فريق TypeScript بـ interface ... extends بدل intersections الكبيرة (A & B & { ... })، لأن العلاقات بين الواجهات تُخزَّن مؤقتًا والواجهة نوع واحد مسطح. أما لشكل كائن بسيط فلا فرق يُذكر.

هل يمكن لصنف أن يطبّق type alias؟

نعم، إذا كان الاسم المستعار نوع كائن (أو intersection من أنواع كائنات): class Point implements PointType { ... } يعمل. لا يستطيع الصنف تطبيق نوع union؛ هذا هو الخطأ TS2422.

هل يمكن لـ interface أن توسّع type alias؟

نعم، ما دام الاسم المستعار نوع كائن: type Base = { id: string }; interface User extends Base { name: string } صحيح. ولا يمكنها توسيع اسم مستعار لـ union. وفي الاتجاه الآخر، يمكن لـ type alias البناء على واجهة باستخدام &.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن