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

استنتاج الأنواع في TypeScript: متى تكتب النوع ومتى لا

تستنتج TypeScript معظم الأنواع من القيم التي تكتبها. تعرّف على ما تستنتجه للمتغيرات، والفرق بين let وconst، والكائنات والمصفوفات والقيم المُعادة، وكيف تحصل دوال الاستدعاء على أنواعها من السياق، وأين ما زلت تحتاج إلى كتابة النوع.

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

استنتاج الأنواع هو أن تستنتج TypeScript نوعًا لم تكتبه. هيّئ متغيرًا فيأخذ نوع القيمة؛ وأعد قيمة من دالة فيتبعها نوع القيمة المُعادة. يحمل معظم كود TypeScript تعليقات أنواع أقل بكثير مما يتوقعه الناس.

تعرض التعليقات ما يظهره المحرر عند التمرير فوق كل اسم. السطر الأخير خطأ ترجمة (TS2322) رغم أن count لم يُكتب له نوع قط؛ ويعلّمه // @ts-expect-error على أنه متوقع حتى يعمل باقي المثال.

let مقابل const: توسيع النوع الحرفي

لا يتغير const أبدًا، لذلك تعطيه TypeScript أضيق نوع ممكن: القيمة الحرفية نفسها. أما let فيحصل على النوع الأوسع، لأنه قد يُعاد إسناده لاحقًا.

let a = "left";    // string
const b = "left";  // "left"
let c = 42;        // number
const d = 42;      // 42
const e = true;    // true

يهم هذا عندما تقبل دالة قيمًا محددة فقط:

index.ts(7,6): error TS2345: Argument of type 'string' is not assignable to parameter of type '"left" | "right"'.

أصلح ذلك بـ const، أو بكتابة نوع المتغير: let dir: "left" | "right" = "left". وللأنواع الحرفية صفحتها الخاصة، الأنواع الحرفية.

الكائنات والمصفوفات توسّع أعضاءها

خصائص الكائن الحرفي قابلة للتغيير، لذلك تتوسع مثل let، حتى عندما يكون الكائن نفسه const. وعناصر المصفوفة تفعل الشيء نفسه. أما as const فيجعل كل شيء للقراءة فقط ويحافظ على الأنواع الحرفية.

استنتاج نوع القيمة المُعادة

يُستنتج نوع القيمة المُعادة للدالة من جمل return فيها. وعندما توجد عدة جمل، يكون الناتج union لأنواعها.

أنواع القيم المُعادة المستنتجة مناسبة لمعظم الدوال. اكتب نوع القيمة المُعادة صراحة في الدوال المصدَّرة أو العامة عندما تريد أن يكون التوقيع عقدًا: عندها يفشل تغيير داخل جسم الدالة يعيد نوعًا مختلفًا عند الدالة نفسها، لا عند مستدعٍ بعيد.

الأنواع السياقية: دوال الاستدعاء تحصل على أنواعها

لا تُستنتج المعاملات من طريقة استدعاء الدالة. هناك استثناءان. المعامل ذو القيمة الافتراضية يأخذ نوع تلك القيمة (function retry(times = 3) يجعل times من نوع number). والدالة المكتوبة في موضع يُتوقع فيه نوع دالة مسبقًا تحصل على أنواع معاملاتها من ذلك السياق، لذلك لا تحتاج معاملات callbacks إلى تعليقات أنواع.

وينطبق الأمر نفسه على forEach وfilter وreduce ودوال Promise ومعالجات الأحداث، وأي دالة تمررها إلى معامل له نوع دالة معلن.

المعاملات تحتاج إلى تعليقات أنواع

دون قيمة افتراضية أو سياق كهذا، لا تنظر TypeScript في طريقة استدعاء الدالة لتخمين أنواع معاملاتها. ومع تفعيل strict يكون المعامل غير المزود بنوع هو الخطأ TS7006:

index.ts(2,17): error TS7006: Parameter 'x' implicitly has an 'any' type.

اكتب function double(x: number) فيطبع المثال 42، مع استنتاج نوع القيمة المُعادة number من x * 2.

النوع المشترك الأفضل

لمصفوفة حرفية تحتوي على أنواع مختلفة من القيم، تستنتج TypeScript مصفوفة من union أنواعها. ولا تبحث عن نوع أساسي مشترك لم تذكره.

المصفوفة الفارغة هي الحالة الوحيدة التي يجب الانتباه إليها. يبدأ const list = [] كمصفوفة «متطورة» يكبر نوعها مع كل push؛ أما التعليق const list: string[] = [] فيصرّح بالنية من البداية وهو أوضح.

متى تكتب النوع

الحالةهل تكتب النوع؟
متغير مهيأ بقيمةلا، القيمة تعطي النوع
متغير معلن دون قيمة (let result;)نعم، let result: string;
معاملات الدوالنعم (إلا في callbacks ذات السياق، أو مع قيمة افتراضية)
نوع القيمة المُعادة لدالة داخلية صغيرةاختياري
نوع القيمة المُعادة لدالة مصدَّرة أو عامةموصى به، كعقد
مصفوفة أو كائن فارغ تملؤه لاحقًانعم، const ids: number[] = []
قيمة يجب أن تحتفظ بأنواعها الحرفيةاستخدم as const
قيمة يجب أن تطابق نوعًا وتحتفظ بنوعها المستنتجاستخدم satisfies

أسرع طريقة لرؤية ما استُنتج هي تمرير المؤشر فوق الاسم في محررك. ودون محرر، أسند القيمة إلى متغير من نوع خاطئ، مثل const probe: boolean = mixed;، فتذكر رسالة خطأ المترجم النوع المستنتج.

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

ما هو استنتاج الأنواع في TypeScript؟

هو أن يستنتج المترجم نوعًا لم تكتبه. يعطي let count = 0 المتغير count النوع number، والدالة التي تعيد a + b لرقمين تحصل على نوع القيمة المُعادة number. ويُفحص النوع المستنتج تمامًا مثل النوع المكتوب.

هل يجب أن أكتب نوع كل متغير في TypeScript؟

لا. اكتب أنواع معاملات الدوال (لا تُستنتج من الاستدعاءات، بل فقط من قيمة افتراضية أو من سياق callback)، وأنواع القيم المُعادة للدوال المصدَّرة إذا أردت واجهة برمجية ثابتة، والمتغيرات المعلنة دون قيمة. ودع TypeScript تستنتج الباقي: const name: string = "Ada" يكرر ما يعرفه المترجم أصلًا.

لماذا تستنتج const نوعًا حرفيًا بينما تستنتج let النوع string؟

لا يمكن إعادة إسناد const أبدًا، لذلك يحصل const dir = "left" على النوع الحرفي "left". أما let فقد يحمل لاحقًا أي نص آخر، لذلك يُوسَّع let dir = "left" إلى string. ويحدث التوسيع نفسه لخصائص الكائن الحرفي، لأن الخصائص قابلة لإعادة الإسناد؛ وas const يمنعه.

لماذا تقول TypeScript إن المعامل له النوع 'any' ضمنيًا؟

هذا هو الخطأ TS7006 من noImplicitAny، وهو جزء من strict. لا تستنتج TypeScript أنواع المعاملات من طريقة استدعاء الدالة، فيكون المعامل المجرد من النوع any. أضف تعليق نوع، function double(x: number)، أو مرّر الدالة في موضع يُعرف فيه نوعها من السياق، مثل callback لـ map.

كيف أرى النوع الذي استنتجته TypeScript؟

مرّر المؤشر فوق الاسم في محررك (يعرض VS Code ومعظم المحررات النوع المستنتج في تلميح). وفي الكود، إسناد القيمة إلى متغير من نوع خاطئ بوضوح يجعل المترجم يطبع النوع المستنتج في رسالة الخطأ.

Coddy programming languages illustration

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

ابدأ الآن