جاوااسکریپت - استفاده از پارامتر «this» در متدهای استاتیک تایپاسکریپت نوع را به کلاس فعلی محدود نمیکند؟
این بهترین نحو با استفاده از یک مثال توضیح داده میشود. من نیاز دارم تا در یک متد استاتیک به کلاس فعلی ارجاع دهم، این همانطور که انتظار میرود کار میکند:
class Cls {
static fn<T extends typeof Cls>(
this: T,
arg: T extends typeof Cls ? true : false,
) {}
}
Cls.fn(true);
Cls.fn(false); // Argument of type 'false' is not assignable to parameter of type 'true'.
با این حال، هنگام فراخوانی Cls.fn از یک متد استاتیک دیگر، کار نمیکند:
class Cls {
static fn1<T extends typeof Cls>(
this: T,
arg: T extends typeof Cls ? true : false,
) {}
static fn2<T extends typeof Cls>(this: T) {
this.fn1(true); // Argument of type 'true' is not assignable to parameter of type 'T extends typeof Cls ? true : false'.
}
}
محیط آزمایشی TS
جالب این است که این کار جواب میدهد اگر جنس fn2 را حذف کنم یا this.fn1<typeof Cls>(true) را انجام دهم. این بدان معناست که خطا مربوط به نوع this است. فکر میکنم مسئله این است که this به هر زیرکلاسی از Cls اشاره میکند، نه دقیقاً به Cls. با این حال، حتی اگر this یک زیرکلاس از Cls باشد، T extends typeof Cls همچنان درست خواهد بود.
آیا این یک باگ است که تایپاسکریپت از مقدار نادرست برای this استفاده میکند؟ اگر این یک باگ نیست، چگونه میتوانم آن را رفع کنم؟
در کد واقعی خود، من نیاز دارم که به کلاس فعلی ارجاع دهم زیرا متد بسته به زیرکلاس، آرگومانهای متفاوتی میپذیرد.
ویرایش: در اینجا یک مثال واقعیتر با استفاده از زیرکلاسها آمده است
پاسخ شماره ۱
این رفتار توسط یک پارامتر نوع عمومی حلنشده توضیح داده میشود.
این توسط یک بیمستقیمی ایجاد میشود که اساساً توسط یک تابع (متد) مرتبه بالاتر معرفی شده است. در مورد یک متد غیرجنریک fn2 (با استفاده از مثال Sub/Base)، استنباط همانطور که انتظار میرود عمل میکند زیرا پارامتر نوع به typeof Sub در کلاس مشتقشده Sub حل میشود:
Base.fn<typeof Sub>(this: typeof Sub, arg: "sub"): void
متأسفانه، در مورد fn3، خود آن یک تابع عمومی (متد) است، که منجر به این میشود که پارامتر عمومی T در فراخوانیهای fn نامشخص باقی بماند، که میتوان آن را از امضای نتیجهگیری شده مشاهده کرد:
Base.fn<T>(this: T, arg: T extends typeof Sub ? "sub" : "base"): void
این مسئله روشن میکند که خطای کامپایلر زیر چه معنی دارد - از آنجا که نوع شرطی نیز هنوز حل نشده است، نه "sub" و نه "base" قابل انتساب به T extends typeof Sub ? "sub" : "base" نخواهند بود:
نوع آرگومان '"sub"' قابل اختصاص به پارامتر از نوع 'T extends typeof Sub ? "sub" : "base"' نیست
همانطور که شما بهدرستی اشاره کردید، میتوان پارامتر نوع عمومی متد مرتبه بالاتر را حذف کرد و بدین ترتیب واسطهگری را از بین برد:
static fn3(this: typeof Sub) {
this.fn('base'); // error
this.fn('sub'); // OK
}
با این حال، این موضوع در کلاسهای مشتقشدهی بعدی مشکل ایجاد میکند اگر شما زمانی به this نیاز داشته باشید:
class Sub {
static fn4(this: typeof Sub) {
this.fn('sub');
return this;
}
}
class SubSub extends Sub {}
SubSub.fn4(); // typeof Sub, probably wanted typeof SubSub
با این حال یک جایگزین وجود دارد - پارامتر T را محدود نکنید و به جای آن نوع this را بر اساس آنچه که T است تعیین شده محدود کنید. تکنیک کلاسیک استفاده از یک نوع شرطی است که به خود never حل میشود:
class Sub extends Base {
static fn3<T>(this: T extends typeof Sub? T: never) {
this.fn('base'); // error
this.fn('sub'); // OK
return this;
}
}
Sub.fn('sub'); // OK
Sub.fn('base'); // error
Sub.fn3(); // typeof Sub
امضای فراخوانی داخلی this.fn() به صورت زیر است:
Base.fn<T extends typeof Sub ? T : never>(this: T extends typeof Sub ? T : never, arg: (T extends typeof Sub ? T : never) extends typeof Sub ? "sub" : "base"): void
با به تعویق انداختن بیشتر ارزیابی از طریق T extends typeof Sub ? T : never در واقع به کامپایلر کمک کردیم: اکنون میداند که T تا زمان نمونهسازی تضمین شده است که typeof Sub است.
زمین بازی
