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

الوراثة في TypeScript: extends وsuper وoverride

وراثة الأصناف في TypeScript: extends وsuper، وإعادة تعريف التوابع بأنواع متوافقة، والكلمة المفتاحية override والخيار noImplicitOverride، والأعضاء protected، وإعادة تعريف الحقول بـ declare، ومتى يكون implements الأداة الأفضل.

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

يرث صنف في TypeScript من صنف آخر باستخدام extends. يحصل الصنف الفرعي على كل حقول الأب وتوابعه، ويستدعي دالة بناء الأب بـ super(...)، ويمكنه إعادة تعريف التوابع، وتتحقق TypeScript من أن كل إعادة تعريف تبقى متوافقة مع أنواع الأب.

هذه وراثة الأصناف في JavaScript (راجع الوراثة في JavaScript لسلسلة الـ prototype التي تقف خلفها). تضيف TypeScript فحوص الأنواع: يمكن إسناد Dog في أي موضع يُتوقع فيه Animal، ويتحقق المترجم من إعادة التعريف ومن استدعاء super.

إعادة تعريف التوابع

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

تغيير المعامل إلى value: string سيكون الخطأ TS2416: Property 'format' in type 'CurrencyFormatter' is not assignable to the same property in base type 'Formatter'. والحلقة توضح السبب: تستدعي format برقم على كل عنصر دون أن تعرف أي صنف فرعي لديها.

هناك ثغرة واحدة. تُقارن معاملات التوابع بشكل ثنائي التغاير (bivariant)، فإعادة التعريف التي تقبل نوعًا أضيق، مثل format(value: 1 | 2)، تُترجم رغم أن الحلقة قد تمرر إليها 1234567. أما أنواع القيم المُعادة فتُفحص بصرامة: إعادة number بدل string هي TS2416.

الكلمة المفتاحية override وnoImplicitOverride

تقول override إن «هذا العضو يحل محل عضو في الصنف الأساسي»، ويلزمك المترجم بذلك. إن لم يكن في الصنف الأساسي عضو كهذا (خطأ إملائي، أو تابع أعاد أحدهم تسميته في الأب) فهذا خطأ:

index.ts(9,14): error TS4117: This member cannot have an 'override' modifier because it is not declared in the base class 'Animal'. Did you mean 'speak'?

دون override يُترجم هذا الخطأ الإملائي وتعيد speak() القيمة "..." بصمت. الكلمة المفتاحية لا تفيد إلا حيث تُكتب، لذلك فعّل noImplicitOverride في tsconfig.json. عندها يجب على كل تابع يعيد تعريف عضو أساسي أن يصرّح بذلك، ويكون speak() العادي في صنف فرعي الخطأ TS4114 (This member must have an 'override' modifier because it overrides a member in the base class 'Animal').

{
    "compilerOptions": {
        "strict": true,
        "noImplicitOverride": true
    }
}

noImplicitOverride ليس جزءًا من strict؛ عليك إضافته بنفسك. وتُحذف override من JavaScript المترجم.

الأعضاء protected في الأصناف الفرعية

الأعضاء private في الأب غير مرئية في الصنف الفرعي. أما الأعضاء protected فمرئية، وهذا سبب وجود protected: إنها الواجهة التي يقدمها الصنف الأب لأبنائه.

راجع معدّلات الوصول للقواعد الكاملة، بما فيها سبب عدم فرض private ولا protected وقت التشغيل.

إعادة تعريف حقل بنوع أضيق

كثيرًا ما يعرف الصنف الفرعي أن حقلًا يحمل شيئًا أكثر تحديدًا مما يقوله الأب. إعادة تعريفه بصيغة pet: Dog لها أثر وقت التشغيل مع حقول الأصناف الحديثة: يُهيَّأ حقل الصنف الفرعي بالقيمة undefined بعد عودة super()، فيمحو ما خزّنته دالة بناء الأب. وما لم تُسنده دالة بناء الصنف الفرعي مجددًا، تبلّغ TypeScript عن ذلك بالخطأ TS2612 (Property 'pet' will overwrite the base property in 'Home'). استخدم declare لتغيير النوع فقط:

لا تولّد declare أي شيء، فتبقى القيمة التي ضبطتها دالة بناء Home. ودالة البناء التي لا تقبل إلا Dog هي ما يجعل النوع الأضيق صحيحًا.

implements مقابل extends

تجيب الكلمتان عن سؤالين مختلفين، ويمكن للصنف أن يستخدم الاثنتين.

extendsimplements
يعمل معصنف واحدأي عدد من الـ interfaces (أو type aliases لأنواع كائنات)
يجلب كودًانعم: الحقول والتوابع ودالة البناءلا، يفحص الشكل فقط
موجود وقت التشغيلنعم، سلسلة الـ prototypeلا، يُحذف
instanceof يعملنعملا
الاستخدام المعتادمشاركة سلوك حقيقيالتعهد بشكل معين لبقية الكود

يمكن للصنف أن يرث من صنف واحد فقط (TS1174: Classes can only extend a single class). لجمع سلوك من عدة مصادر، طبّق عدة interfaces وشارك الكود عبر التركيب (حقل يحمل كائنًا مساعدًا) أو الـ mixins.

الـ Mixins

الـ mixin دالة تأخذ صنفًا وتعيد صنفًا فرعيًا منه. تعطي شكلًا من الوراثة المتعددة يجتاز فحص الأنواع:

النوع any[] في Constructor ضروري: لا تقبل TypeScript أساسًا لـ mixin إلا إذا كانت دالة بنائه تأخذ معامل rest واحدًا من النوع any[]. تعمل الـ mixins لكنها تجعل قراءة الأنواع وتتبعات المكدس أصعب، فابدأ بالتركيب أولًا.

أخطاء شائعة

  • استدعاء تابع قابل لإعادة التعريف من دالة البناء الأساسية. تعمل دالة البناء الأساسية قبل تهيئة حقول الصنف الفرعي، فإعادة التعريف التي تقرأ this.items ترى undefined، واستدعاء مثل this.items.push(x) يرمي TypeError وقت التشغيل. لا تكتشف TypeScript ذلك.
  • نسيان super(...) أو استدعاؤه متأخرًا. الخطآن TS2377 وTS17009؛ يجب أن يبني الأب الكائن قبل أن يلمس الصنف الفرعي this.
  • إعادة تعريف حقل من الأب دون declare. يعيد حقل الصنف الفرعي ضبطه إلى undefined.
  • الهرميات العميقة. كل مستوى يربط الابن بتفاصيل الأب الداخلية. مستويان يكفيان عادة؛ وبعد ذلك فضّل التركيب أو الـ interfaces.

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

كيف تعمل الوراثة في TypeScript؟

يرث صنف من آخر باستخدام extends: class Dog extends Animal. يحصل الصنف الفرعي على حقول الأب وتوابعه، ويستدعي دالة بناء الأب بـ super(...)، ويمكنه إعادة تعريف التوابع. وتتحقق TypeScript من أن إعادات التعريف تبقى متوافقة في الأنواع مع الأب.

ماذا تفعل الكلمة المفتاحية override في TypeScript؟

تعلّم override تابعًا أو خاصية تحل محل عضو من الصنف الأساسي. إن لم يكن في الصنف الأساسي عضو بهذا الاسم، يبلّغ المترجم TS4117، فيكتشف الأخطاء الإملائية والأعضاء التي أُعيدت تسميتها في الأب. ومع تفعيل noImplicitOverride يجب أن تحمل كل إعادة تعريف هذه الكلمة (وإلا فالخطأ TS4114).

كيف أستدعي تابعًا من الصنف الأب في TypeScript؟

استخدم super.methodName(...) داخل تابع الصنف الفرعي. وفي دالة البناء يستدعي super(...) دالة بناء الأب، ويجب أن يُنفَّذ قبل أي استخدام لـ this.

هل تدعم TypeScript الوراثة المتعددة؟

لا. يمكن للصنف أن يرث من صنف واحد فقط (TS1174: Classes can only extend a single class). ويمكنه تطبيق أي عدد من الـ interfaces، والـ mixins (دوال تأخذ صنفًا وتعيد صنفًا فرعيًا منه) تجمع السلوك من عدة مصادر.

ما الفرق بين extends وimplements؟

extends يرث كودًا حقيقيًا من صنف أب. أما implements فيتحقق فقط من أن الصنف يطابق شكل interface ولا يضيف شيئًا وقت التشغيل. ويمكن للصنف أن يفعل الأمرين: class Dog extends Animal implements Pet.

Coddy programming languages illustration

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

ابدأ الآن