الگوی پارامترهای عمومی TypeScript که گاهی اوقات میتوانند undefined باشند؟
من میخواهم یک سلسله مراتب کلاسی داشته باشم که در برخی پیادهسازیهای یک کلاس پایه اجازه دهد یک عضو تعریف نشده باشد، در حالی که در دیگر پیادهسازیها این هرگز اتفاق نخواهد افتاد. من تمام روز سعی کردهام بفهمم چگونه میتوانم این سؤال را مطرح کنم، زیرا چند روش مختلف را امتحان کردهام و بسته به کاری که انجام دادهام با محدودیتهای مختلف زبان مواجه شدهام. سعی میکنم مسئله را با مثالی از یکی از روشهایی که امتحان کردهام، خلاصه کنم:
function f(foo: Foo): number { return foo.bar; }
class Foo { bar: number; }
abstract class Base<T extends Foo | undefined> {
data: T;
public f(): number {
if (!this.data) { return -1; }
return f(this.data); // bad: this.data is still `Foo | undefined` not just `Foo`
}
}
class Always extends Base<Foo> {
constructor() { super(); this.data = new Foo(); }
}
class Sometimes extends Base<Foo | undefined> {}
let a = new Always();
let s = new Sometimes();
a.data.bar = 1; // good: no error because `a.data` must not be undefined
s.data.bar = 1; // good: compiler flags this because s.data can be undefined
خط بالایی که من آن را «بد» علامتگذاری کردهام مشکل است -- چون شما هنوز نمیتوانید یک ژنریک اجتماع را محدود کنید، هیچ راهی برای استفاده از یک محافظ ساده برای اطمینان از اینکه data تعریف شده نیست وجود ندارد. البته من میتوانم f(this.data as Foo) بنویسم، اما میخواهم اگر ممکن است از انجام این کار در همه جاهایی که به آن ارجاع میدهم اجتناب کنم.
من بهطور کوتاه با نوعهای شرطی بازی کردهام با افزودن یک پارامتر عمومی دوم که extends boolean باشد، اما هنوز نتوانستهام آن را کارا کنم. اساساً، تمام راهحلهایی که خودم ارائه دادهام یکی از ۳ خط با کامنت در مثال را خراب میکنند -- f(this.data)، a.data.bar همیشه معتبر است، یا s.data.bar همیشه نامعتبر است اگر قبلاً بررسی نکرده باشید که s.data تعریف شده باشد.
پاسخ شماره ۱
اگر آماده باشید از یک پارامتر نوع دوم برای نمایش احتمال مقدار نامشخص استفاده کنید، این کار میکند:
function f(foo: Foo): number { return foo.bar; }
class Foo { bar: number; }
abstract class Base<T extends Foo, TUndefiend extends undefined> {
data!: T | TUndefiend;
public f(): number {
if (!this.data) { return -1; }
return f(this.data); // ok now
}
}
class Always extends Base<Foo, never> {
constructor() { super(); this.data = new Foo(); }
}
class Sometimes extends Base<Foo, undefined> {}
let a = new Always();
let s = new Sometimes();
a.data.bar = 1; // good: no error because `a.data` must not be undefined
s.data.bar = 1; // good: compiler flags this because s.data can be undefined
محافظهای نوع به پارامتر نوع محدود نخواهند شد، اما اگر دو پارامتر نوع مختلف در یک اتحاد وجود داشته باشند، به نظر میرسد که به خوبی کار میکند.
پارامتر نوع اضافی (TUndefined) اگر وارد شویم تبخیر می شود چون T | هرگز T نیست و همه چیز طبق انتظار در کلاس Always مشتق شده کار می کند. اگر در تعریف نشده عبور کنیم، باید برای تعریف نشده بررسی کنیم، باز هم همان طور که انتظار می رود در گاهی اوقات
