محدودیتهای جنریک در تایپاسکریپت با استفاده از گسترش و پارامترهای پیشفرض


بنابراین من در تلاش هستم تا تابعی بنویسم که یک جنریک داشته باشد که یک شیء مشخص را گسترش میدهد و بنابراین آن را محدود میکند. سپس میخواهم این جنریک را همراه با تعریف یک پارامتر برای ایجاد یک پارامتر جدید «تقویتشده» استفاده کنم. همه اینها خوب است، اما به محض اینکه میخواهم یک مقدار پیشفرض برای پارامتر معرفی کنم، TypeScript با پیامی مانند زیر شکایت میکند (چندین نسخه مختلف از این در playground):

تابع:

const test1 = <T extends { foo?: string }>(options: T & { bar?: boolean } = {foo: 
''}) => {
    console.log(options);
}

خطا:

نوع '{ foo: string; }' قابل انتساب به نوع 'T & { bar?: boolean; }' نیست. شیء صریح ممکن است فقط ویژگیهای شناختهشده را مشخص کند، اما 'foo' در نوع 'T & { bar?: boolean; }' وجود ندارد. آیا منظور شما نوشتن 'foo' بود؟

کامپایلر به من هشدار میدهد که احتمالاً میخواستم از foo استفاده کنم، که در واقع هم همین کار را کردم. آیا به سادگی امکان استفاده از یک جنریک به این روش وجود ندارد یا این یک باگ در تایپاسکریپت است؟

پاسخ شماره ۱

نوع T در زمان تعریف ناشناخته است، بنابراین کامپایلر این خطا را میدهد که نمیتوانید چیزی را که از آن اطلاعی ندارید مقداردهی اولیه کنید. چند راهحل جایگزین به ذهنم میرسد، اما مطمئن نیستم که چقدر برای مورد شما مفید باشند.

میتوانید نوع T را همانطور که هست رها کنید و برای پارامتر options از نوعهای اتحادی به صورت زیر استفاده کنید:

const test1 = <T> (options: T | { bar?: boolean } | { foo?: string } = { foo: '' }) => {
  console.log(options);
};

راه حل دیگری استفاده از یک assertion نوع است و به صورت دستی به کامپایلر میگوید که مقداردهنده اولیه از نوعی است که باید باشد:

const test2 = <T extends { foo?: string }>(options: T & { bar?: boolean } = { foo: '' } as T & { bar?: boolean }) => {
  console.log(options);
};

اما به یاد داشته باشید که اینها فقط راهحلهای موقتی هستند و هر زمان که مجبور به استفاده از یک راهحل موقتی باشید، این به معنای وجود یک نقص در طراحی است. شاید بتوانید یک نگاه دوباره به منطق خود بیندازید و آن را بهبود دهید تا نیاز به این راهحلهای موقتی از بین برود. یا شاید بتوانید مقداردهی اولیه آرگومان را نادیده بگیرید و options.foo=options.foo || ''; را به عنوان اولین خط کد در تابع خود اضافه کنید. فقط چند ایده بود.

پاسخ شماره ۲

دلیل اینکه هیچ یک از مقداردهیهای اولیه کار نمیکند این است که شما نمیتوانید چیزی را مقداردهی کنید که نمیدانید. تماس زیر را در نظر بگیرید:

test1<{ foo: string, goo: boolean}>();

پارامتر عمومی برای تابع معتبر است، اما یک ویژگی اضافی دارد، goo، که الزامی است، اما در مقدار پیشفرض شما برای گزینهها مشخص نشده است. به همین دلیل کامپایلر دربارهی تخصیص شکایت میکند، شما شکل نهایی T را نمیدانید، شما فقط حداقل الزامات آن را میدانید، بنابراین نمیتوانید یک شیء بسازید که با T سازگار باشد.

اگر با این موضوع که options همه ویژگیهای اجباری مشخصشده در پارامتر نوع عمومی را نداشته باشد، مشکلی ندارید، میتوانید از تأیید نوع استفاده کنید

const test1 = <T extends { foo?: string }>(options: T & { bar?: boolean } = <any>{foo: ''}) => {
    console.log(options);
}