المتغير متعدد الأشكال this في TypeScript


أحاول القيام بما أعتقد أنه حالة استخدام "نموذجية" لـ this متعدد الأشكال ولكنه لا يعمل ولا أستطيع فهم ذلك. تخيل أن لدي بعض abstract الفئة الأساسية التي يمكن استنساخها، على سبيل المثال:

abstract class X {
    abstract clone(): this;
}

الآن أريد تنفيذ فئة أساسية توفر تنفيذ clone:

class Y extends X {
    constructor(private x: number) { super() }
    clone(): this { return new Y(this.x + 1); }
}

عندما أفعل هذا، أحصل على خطأ يقول Type Y is not assignable to type 'this'. أنا مرتبك تمامًا. كل ما أريد توضيحه هنا هو قيد النوع الذي ينص على أنه إذا تم استدعاء طريقة clone لفئة فرعية من X، فإن نوع الشيء الذي ستستعيده سيكون مطابقًا للنوع الفرعي. أليس هذا بالضبط ما من أجله يُستخدم this المتعدد الأشكال؟ ما الخطأ الذي أفعله هنا؟

إليك رابط هذا الكود في ملعب TypeScript.

الإجابة رقم 1

لقد علّمت إجابة آرتيم على أنها الإجابة الصحيحة لأنه يبدو أنه على حق في أنه فعليًا لا يمكن القيام بذلك بطريقة آمنة 100٪.

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

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

class Y extends X {
    constructor(private x: number) { super() }
    clone(): this {
        return new Y(this.x + 1) as this;
    }
}

يمكنك رؤية نسخة كاملة يتم تجميعها هنا.

الإجابة رقم 2

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

بعبارة أخرى، بالنظر إلى الإعلان abstract clone(): this، يجب أن يكون هذا الكود صالحًا حتى لو لم تقم Z بإعادة تعريف clone نفسها، لكن تنفيذك لـ clone في Y يكسره.

class Z extends Y {
    f() {
        let c: Z = this.clone();
    }
}

لذا يبدو أن الطريقة التي لها نوع الإرجاع this يجب أن دائماً return this.

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