استنتاج الأنواع في TypeScript
مقدمة
سنُغطّي في هذا القسم آلية استنتاج الأنواع في TypeScript، خاصّةً أين وكيف تُستنتَج الأنواع.
الأساسيات
هناك العديد من الأماكن في TypeScript التي يُستعمَل فيها استنتاج الأنواع لتوفير معلومات النوع عندما لا تكون هناك حاشية نوع (type annotation) صريحة. على سبيل المثال، في الشيفرة التالية
let x = 3;
سيُستَنتَجُ نوع المتغيّر x
على أنّه النوعُ number
. استنتاج الأنواع هذا يكون عند تهيئة (initializing) المتغيرات والعناصر، أو عند ضبط قيم افتراضيّة للمعاملات، أو عند تحديد النوع المُعادِ لدالةٍ ما.
يكون استنتاج الأنواع في معظم الحالات واضحًا ومباشرًا. سنتعرّف في ما يلي من الأقسام على بعض الفروقات الدقيقة والاختلافات الصغيرة في آلية استنتاج الأنواع.
أفضل نوعٍ مشترك (Best common type)
عندما يُستنتَج النوع من عدّة تعابير، فإنّ أنواعَ هذه التعابير تُستعمَل لحساب "أفضل نوعٍ مشترك". مثلًا:
let x = [0, 1, null];
لاستنتاج نوع x
في المثال أعلاه، علينا أخذ نوع كل عنصر من عناصر المصفوفة بعين الاعتبار. يكون لدينا هنا خياران لنوع المصفوفة: إمّا number
أو null
. تأخذ خوارزمية أفضل نوعٍ مشترك كلّ نوعٍ مُرشَّحٍ (candidate type) بعين الاعتبار، وتختار النوع المتوافق مع بقية الأنواع المُرشَّحة.
ولأنّ أفضل نوعٍ مشترك يجب أن يُنتقى من الأنواع المرشحّة، فهناك بعض الحالات التي تشترك فيها الأنواع في هيكلها دون أن يكون هناك نوع واحد يُعتبر نوعًا أبًا مُسيطرًا (أي أن جميع الأنواع ترث منه) على بقية الأنواع المرشّحة. مثلاً:
let zoo = [new Rhino(), new Elephant(), new Snake()];
كوضعٍ مثاليّ، قد نرغب بأن يُستنتَج نوعُ المتغير zoo
ليكون Animal[]
، لكن لعدم وجود أي كائن من النوع Animal
صراحةً في المصفوفة، فإنّنا لا نستنتِج نوع عناصر المصفوفة. لإصلاح هذا، يجب توفير النوع عندما لا يكون أي نوع من أنواع العناصر مُسيطرًا على بقية الأنواع:
let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];
عندما لا يوجد أي نوعٍ مشترك مسيطر، فالاستنتاج سيكون نوع مصفوفة الاتحاد (Rhino | Elephant | Snake)[]
.
الأنواع السياقية (Contextual Type)
استنتاج الأنواع يعمل بشكل مُعاكس كذلك في بعض الحالات في لغة TypeScript، ويُسمّى بالأنواع السياقية (contextual typing). تكون الأنواع السياقية عندما يكون نوع تعبير معيّن ضمنيّا (implied) يُمكن معرفتُه من مكانه. على سبيل المثال:
window.onmousedown = function(mouseEvent) {
console.log(mouseEvent.button); //<- خطأ
};
تُطلِق الشيفرة أعلاه خطأ نوعٍ (type error) لأنّ مدقّق الأنواع في لغة TypeScript استَعمَل نوع الدالة Window.onmousedown
لاستنتاج نوع تعبير الدالة على الجانب الأيمن من التعيين. وبذلك استُنتِج نوع المعامل mouseEvent
. لو لم تكن هذه الدالة في مركز يُمكن فيه استخدام الأنواع السياقية، لَكَان لنوعِ المعامل mouseEvent
النوعُ any
، ولمَا كان هناك أي خطأ.
إن احتوى التعبير ذو النوع السياقي على معلومات نوعٍ صريحة، فسيُتجاهَل النوع السياقي. فلو كتبنا المثال أعلاه كما يلي:
window.onmousedown = function(mouseEvent: any) {
console.log(mouseEvent.button); //<- لا أخطاء الآن
};
فإنّ تعبير الدالة ذو حاشية نوع المعامل الصريحة ستُغطّي على النوع السياقيّ. وحالما تفعل ذلك، فلن تكون هناك أية أخطاء لأن النوع السياقي لم يعد مُطبَّقًا.
تعمل الأنواع السياقية في الكثير من الحالات. وتشمل الحالاتُ الشائعةُ قيمَ المعاملات التي تُمرَّر إلى استدعاءات الدوال، والجانب الأيمن للتعيين، وإثباتات الأنواع (type assertions)، وعناصر الكائنات، وقيم المصفوفات الحرفيّة (array literals)، وجُمل الإعادة (return statements). يعمل النوع السياقي كذلك كنوعٍ مرشَّحٍ في خوارزمية أفضل نوعٍ مشترك. على سبيل المثال:
function createZoo(): Animal[] {
return [new Rhino(), new Elephant(), new Snake()];
}
في هذا المثال، هناك أربعة مُرشَّحين لخوارزمية أفضل نوعٍ مشترك: Animal
، وRhino
، وElephant
، وSnake
. من هذه الأنواع المرشحة، سيُنتقى النوع Animal
من طرف الخوارزمية لأنّ جميع الأنواع الأخرى ترث منه.