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

أنواع الدوال في TypeScript: صياغة السهم وأنواع callbacks

كيف تكتب نوع دالة في TypeScript: صياغة السهم (a: number) => string، وtype aliases للدوال، وتحديد أنواع الدوال السهمية والـ callbacks، وcall signatures وconstruct signatures، ولماذا النوع Function متساهل أكثر من اللازم.

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

يصف نوع الدالة معاملات الدالة وقيمتها المُعادة، ويُكتب بسهم: (a: number, b: number) => number. سمّه بـ type فتستطيع استخدامه للمتغيرات والمعاملات وخصائص الكائنات.

الدالة المسندة إلى add لا تحتاج إلى تعليقات أنواع: تأتي أنواع معاملاتها من Operation. يُسمى هذا الأنواع السياقية (contextual typing)، وهو السبب الرئيسي لتسمية أنواع الدوال.

صياغة نوع الدالة

الشكل هو (parameters) => ReturnType. يحتاج كل معامل إلى اسم ونوع. الأسماء موجودة لسهولة القراءة ولتلميحات المحرر؛ فأي دالة بأنواع معاملات متوافقة تطابق، أيًا كانت الأسماء التي تستخدمها.

type Predicate = (value: number) => boolean;
type Formatter = (value: number, digits?: number) => string; // optional parameter
type Logger = (...parts: string[]) => void;                    // rest parameter
type Factory = () => { id: number };                           // no parameters

الاسم ليس اختياريًا. يعلن (string) => void معاملًا اسمه string بلا نوع، ويرفضه الوضع الصارم بـ TS7051: Parameter has a name but no type. Did you mean 'arg0: string'?. اكتب (value: string) => void.

تحديد أنواع الدوال السهمية

يمكن تحديد نوع الدالة السهمية بطريقتين. حدد نوع الدالة نفسها، أو حدد نوع المتغير بنوع دالة ودع المعاملات تُستنتج.

الصيغة 1 هي الخيار المعتاد لدالة مستقلة. وتؤتي الصيغة 3 ثمارها عندما تشترك عدة دوال في توقيع واحد (معالجات، ودوال مقارنة، ودوال تحويل)، لأن التوقيع يُكتب مرة واحدة وأي تغيير فيه يُفحص في كل مكان.

أنواع Callbacks

المعامل الذي نوعه نوع دالة هو callback. تُفحص دالة المستدعي مقابله، وتُستنتج معاملاتها منه.

قاعدتان تجعلان استخدام الـ callbacks مريحًا:

  • المعاملات الأقل مقبولة. يمكن تمرير دالة تأخذ معاملًا واحدًا حيث يُعرض معاملان. تتجاهل JavaScript الوسائط الإضافية، فتسمح TypeScript بذلك. أما المعاملات الأكثر مما يعرضه النوع فخطأ (Target signature provides too few arguments.).
  • القيمة المُعادة void تقبل أي شيء. الـ callback الذي نوعه (...) => void يمكن أن يعيد قيمة؛ والمستدعي يتعهد بعدم استخدامها. ولهذا يُترجم list.forEach((x) => other.push(x)) رغم أن push تعيد رقمًا.

يجب أن تظل أنواع المعاملات متوافقة. تمرير (x: string) => ... حيث يُتوقع (a: number, b: number) => number يفشل بـ Types of parameters 'x' and 'a' are incompatible.

Call Signatures

(n: number) => string اختصار لنوع كائن فيه call signature: { (n: number): string }. تحتاج إلى الصيغة الطويلة عندما تحمل الدالة خصائص أيضًا.

تتيح لك TypeScript إضافة خصائص إلى دالة معلنة بـ const في النطاق نفسه، وتتتبعها في نوع الدالة. ويمكن للواجهة أن تحمل call signature أيضًا: interface Counter { (): number; count: number }.

يصف construct signature شيئًا يُستدعى بـ new. يضيف new في البداية: new (name: string) => User، أو { new (name: string): User } بصيغة الكائن. وبه تحدد نوع معامل يستقبل صنفًا.

صياغة method مقابل صياغة الخاصية

في نوع كائن، يمكن كتابة عضو الدالة كـ method، handle(value: string): void، أو كخاصية تحمل دالة، handle: (value: string) => void. يبدوان قابلين للاستبدال لكنهما يُفحصان بشكل مختلف تحت strict:

يبلّغ المترجم:

index.ts(8,27): error TS2322: Type '(value: string) => void' is not assignable to type '(value: string | number) => void'.
  Types of parameters 'value' and 'value' are incompatible.
    Type 'string | number' is not assignable to type 'string'.
      Type 'number' is not assignable to type 'string'.

الخطأ في محله: onlyStrings لا تستطيع التعامل مع رقم. أما صيغة method فتمرر الخطأ نفسه (معاملات methods تُفحص «bivariantly» لأسباب تاريخية)، وكان a.handle(42) سينهار وقت التشغيل بـ TypeError: value.toUpperCase is not a function. احذف سطر b لترى هذا الانهيار. عندما تكتب أنواع كائناتك، تكتشف صيغة الخاصية أخطاء أكثر.

تجنب النوع Function

Function هو النوع المدمج الذي تحققه كل قيمة دالة. ونادرًا ما يكون ما تريده: لا تعرف TypeScript المعاملات ولا نوع القيمة المُعادة، فيُقبل كل استدعاء ويعيد any.

استبدل Function بالتوقيع الحقيقي. وعندما تقبل فعلًا أي دالة (مثل debounce عامة أو غلاف للتسجيل)، يقبل (...args: never[]) => unknown كل الدوال ويُبقي الناتج unknown بدل any. وللدوال التي تعتمد أنواعها على مدخلاتها، انظر الأنواع العامة: نوع الدالة العامة يبدو هكذا <T>(value: T) => T.

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

كيف أعرّف نوع دالة في TypeScript؟

استخدم صياغة السهم: (a: number, b: number) => number. سمّه بـ type alias، type Compare = (a: number, b: number) => number;، واستخدم ذلك الاسم للمتغيرات والمعاملات والخصائص. أسماء المعاملات جزء من الصياغة لكنها للتوثيق فقط: الدالة ذات أسماء المعاملات المختلفة تطابق رغم ذلك.

كيف أحدد نوع معامل callback في TypeScript؟

اكتب نوع الدالة كنوع للمعامل: function onEach(items: string[], cb: (item: string, index: number) => void) { ... }. يمكن للمستدعين تمرير دالة تأخذ معاملات أقل، وتُستنتج معاملات الـ callback من ذلك النوع، فلا يحتاج onEach(list, (item) => ...) إلى أي تعليق نوع.

لماذا يجب ألا أستخدم النوع Function في TypeScript؟

يقبل Function أي دالة، واستدعاؤه لا يُفحص: أي وسائط مسموحة والناتج any. اكتب التوقيع الحقيقي بدلًا منه، مثل () => void أو (value: string) => number، أو (...args: never[]) => unknown عندما تقبل فعلًا كل الدوال.

ما الفرق بين نوع الدالة و call signature؟

يصفان الشيء نفسه. (n: number) => string اختصار لنوع الكائن { (n: number): string }. وصيغة call signature لازمة عندما تكون للدالة خصائص أيضًا، مثل { (n: number): string; label: string }.

كيف أحدد نوع دالة سهمية في TypeScript؟

إما أن تحدد أنواع معاملاتها ونوع قيمتها المُعادة مضمّنة، const half = (n: number): number => n / 2;، أو أن تعطي المتغير نوع دالة وتترك المعاملات تُستنتج: const half: (n: number) => number = (n) => n / 2;.

Coddy programming languages illustration

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

ابدأ الآن