ما الفرق بين "declare class" و"interface" في TypeScript


في TypeScript، عند إنشاء ملفات إعلان المصدر .d.ts، أيهما يفضل ولماذا؟

declare class Example {
    public Method(): void; 
}

أو

interface Example {
    Method(): void;
}

الاختلافات التي أستطيع أن ألاحظها هي أن الواجهات لا يمكن أن تحتوي على طرق ثابتة، لذا يجب عليك استخدام فئة لذلك. كلاهما لا يُنتج أي ناتج JS، لذا ربما لا يهم؟

الإجابة رقم 1

واجهة تُستخدم عندما تريد ببساطة وصف شكل كائن. لا يوجد أي توليد للكود، على الإطلاق، للواجهات -- فهي مجرد عنصر في نظام الأنواع. لن ترى أي فرق في توليد الكود لفئة اعتمادًا على ما إذا كانت تحتوي على عبارة يطبق أم لا.

`declare class` تُستخدم عندما تريد وصف فئة موجودة بالفعل (عادة فئة TypeScript، لكن ليس دائمًا) والتي ستكون متاحة خارجيًا (على سبيل المثال، لديك ملفان .ts يتم تحويلهما إلى ملفين .js وكلاهما مضمّن عبر وسم `script` في صفحة ويب). إذا ورثت من `class` باستخدام `extends` (بغض النظر عما إذا كان النوع الأساسي هو `declare class` أو `class` عادي) فإن المُجمّع سوف يولّد كل الشيفرة اللازمة لربط سلسلة النماذج الأولية (prototype) والمُنشئين المتوجهين وما إلى ذلك.

إذا حاولت الوراثة من declare class التي كان يجب أن تكون واجهة، فسوف تواجه خطأ أثناء التشغيل لأن الكود الناتج سيشير إلى كائن لا يمتلك تمثيلاً وقت التشغيل.

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

لتصبح فعليًا مفرط الذكاء، إذا كان لديك خلفية عن C++، يمكنك أن تفكر تقريبيًا في interface على أنه typedef وdeclare class على أنه إعلان extern للباني الذي يفتقر تمامًا إلى تعريف في وحدة الترجمة هذه.

من ناحية الاستهلاك البحتة (كتابة كود إجباري، وليس إضافة أنواع جديدة)، الفرق الوحيد بين interface و declare class هو أنه لا يمكنك new لإنشاء واجهة. ومع ذلك، إذا كنت تعتزم extend/implement أحد هذه الأنواع في class جديد، فيجب عليك بالتأكيد اختيار النوع الصحيح بين interface و declare class. واحد فقط منهما سيعمل.

قاعدتان ستفيدانك جيدًا:

  • هل اسم النوع يتماشى مع دالة منشئ (شيء يمكن استدعاؤه باستخدام new) موجود فعليًا في وقت التشغيل (على سبيل المثال Date موجود، لكن JQueryStatic غير موجود)؟ إذا كانت الإجابة لا، فأنت بالتأكيد تريد interface
  • هل أتعامل مع صنف مُجمَّع من ملف TypeScript آخر، أم شيء مشابه بما فيه الكفاية؟ إذا نعم، استخدم declare class

الإجابة رقم 2

يمكنك تنفيذ الواجهة:

class MyClass implements Example {
    Method() {

    }
}

بينما يُقصد من بناء الجملة declare class حقًا أن يُستخدم لإضافة تعريفات النوع للكود الخارجي الذي لم يُكتب بلغة TypeScript - بحيث يكون التنفيذ "في مكان آخر".

الإجابة رقم 3

بعبارات بسيطة، يُستخدم declare في ملفات .ts/d.ts لإخبار المترجم بأنه يجب أن نتوقع وجود الكلمة المفتاحية التي نقوم بـ declaring لها في ذلك البيئة، حتى لو لم تكن معرفة في الملف الحالي. هذا سيسمح لنا بعد ذلك بالحصول على أمان النوع عند استخدام الكائن المعلن عنه، حيث يعرف مترجم Typescript الآن أن بعض المكونات الأخرى قد توفر تلك المتغير.

الإجابة رقم 4

الفرق بين declare و interface في TS:

يعلن:

declare class Example {
    public Method(): void; 
}

في الكود أعلاه، تتيح declare لمترجم TS معرفة أن الكلاس Example مُعلن في مكان ما. هذا لا يعني أن الكلاس مضمن بطريقة سحرية. أنت كمبرمج مسؤول عن توفر الكلاس عند إعلانه (باستخدام كلمة declare الأساسية).

الواجهة:

interface Example {
    Method(): void;
}

الـ interface هو هيكل افتراضي موجود فقط داخل تايب سكريبت. يستخدمه مترجم تايب سكريبت لغرض واحد فقط وهو التحقق من النوع. عندما يتم ترجمة الكود إلى جافاسكريبت، سيتم إزالة هذا الهيكل بالكامل. يستخدم مترجم تايب سكريبت الواجهات للتحقق مما إذا كانت الكائنات لديها البنية الصحيحة.

على سبيل المثال، عندما يكون لدينا الواجهة التالية:

interface test {
  foo: number,
  bar: string,
}

الأشياء التي نحددها والتي لها هذا النوع من الواجهة تحتاج إلى أن تتطابق مع الواجهة بالضبط:

// perfect match has all the properties with the right types, TS compiler will not complain.
  const obj1: test = {   
    foo: 5,
    bar: 'hey',
  }