تولید پویا نوع بازگشتی بر اساس پارامتر آرایهای از اشیاء در تایپاسکریپت
من در حال تلاش برای تعریف یک تعریف TypeScript مانند زیر هستم:
interface Config {
fieldName: string
}
function foo<T extends Config>(arr: T[]): Record<T['fieldName'], undefined>
function foo(arr: Config[]): Record<string, undefined> {
const obj = {}
_.forEach(arr, entry => {
obj[entry.fieldName] = undefined
})
return obj
}
const result = foo([{fieldName: 'bar'}])
result.baz // This should error since the array argument did not contain an object with a fieldName of "baz".
کد فوق به شدت از "تولید دینامیک نوع بازگشتی بر اساس پارامتر در TypeScript" الهام گرفته است، که تقریباً دقیقاً همان چیزی است که من میخواهم انجام دهم، با این تفاوت که پارامتر من یک آرایه از اشیا است به جای یک آرایه از رشتهها.
مشکل این است که نوع result برابر با Record<string, undefined> است در حالی که من میخواهم آن Record<'bar', undefined> باشد. من کاملاً مطمئنم تعریف بازگشتی من از Record<T['fieldName'], undefined> درست نیست (چون T یک آرایه از اشیاء شبیه Config است) اما نمیتوانم بفهمم چگونه میتوان آن را به درستی با نوع جنریک مشخص کرد.
هر گونه کمکی بسیار قدردانی میشود.
پاسخ شماره ۱
سایر پاسخهای اینجا مشکل را شناسایی کردهاند: تایپاسکریپت تمایل دارد نوع استنباط شده یک مقدار رشتهای ثابت را از نوع رشته ثابت خود (مثل "bar") به string گسترده کند. راههای مختلفی برای گفتن به کامپایلر برای جلوگیری از این گستردهسازی وجود دارد که برخی از آنها در سایر پاسخها پوشش داده نشدهاند.
یکی از راهها این است که فراخواننده foo() نوع "bar" را به عنوان "bar" و نه string نشانهگذاری یا تأیید کند. برای مثال:
const annotatedBar: "bar" = "bar";
const resultAnnotated = foo([{ fieldName: annotatedBar }]);
resultAnnotated.baz; // error as desired
const resultAssertedBar = foo([{ fieldName: "bar" as "bar" }]);
resultAssertedBar.baz; // error as desired
نسخه ۳.۴ تایپاسکریپت const assertions را معرفی کرد، روشی برای فراخوان تابع foo() تا نوع دقیقتری درخواست کند بدون اینکه مجبور باشد نوع را بهصورت صریح بنویسد:
const resultConstAsserted = foo([{ fieldName: 'bar' } as const]);
resultConstAsserted.baz; // error as desired
اما تمام این موارد نیاز دارند که فراخوانگر foo() آن را به روش متفاوتی فراخوانی کند تا رفتار غیرگسترشی مورد نظر حاصل شود. به طور ایدهآل، امضای نوع foo() به گونهای تغییر مییافت که رفتار غیرگسترشی مورد نظر به طور خودکار وقتی به صورت معمولی فراخوانی میشود، رخ دهد.
خب، خبر خوب این است که شما میتوانید این کار را انجام دهید؛ خبر بد این است که نمادگذاری برای انجام این کار عجیب است:
declare function foo<
S extends string, // added type parameter
T extends { fieldName: S },
>(arr: T[]): Record<T['fieldName'], undefined>;
اول بیایید مطمئن شویم که کار میکند:
const result = foo([{ fieldName: "bar" }]);
result.baz; // error as desired
خوبه. فراخوانی معمولی foo() حالا خروجی Record<"bar", undefined> را میدهد.
اگر این شبیه جادو به نظر میرسد، موافقم. تقریباً میتوان اینطور توضیح داد که اضافه کردن یک پارامتر نوع جدید S extends string به کامپایلر نشان میدهد که باید نوعی محدودتر از فقط string استنباط کند، و بنابراین T extends {fieldName: S} تمایل دارد به عنوان یک نام فیلد با یک رشته ثابت fieldName استنباط شود. و در حالی که T واقعاً به عنوان {fieldName: "bar"} استنباط میشود، پارامتر نوع S به عنوان string و نه "bar" استنباط میشود. چه کسی میداند.
من دوست داشتم بتوانم به این سؤال به روشی آشکارتر یا سادهتر برای تغییر نوع امضا پاسخ دهم؛ شاید چیزی شبیه function foo<T extends { filename: const string}>(...). در واقع مدتی پیش من microsoft/TypeScript#30680 را ثبت کردم تا این پیشنهاد را بدهم؛ تا کنون اتفاق زیادی نیفتاده است. اگر فکر میکنید مفید است، ممکن است بخواهید به آنجا بروید و یک 👍 بدهید.
در نهایت، @kaya3 مشاهدهٔ دقیقی انجام میدهد: امضای نوع foo() واقعاً به نظر نمیرسد که خود T برایش مهم باشد؛ تنها کاری که با T انجام میدهد این است که مقدار fieldName را جستجو کند. اگر این برای مورد استفادهٔ شما صادق است، میتوانید امضای foo() را بهطور قابل توجهی ساده کنید، به شرطی که تنها به S توجه کنید:
declare function foo<S extends string>(arr: { fieldName: S }[]): Record<S, undefined>;
const result = foo([{ fieldName: "bar" }]);
result.baz; // error as desired
و این حتی به نظر نمیرسد که جادوگری سیاه خیلی زیادی باشد، زیرا S بهعنوان "bar" استنباط شده است.
خب، امیدوارم این کمک کند. موفق باشید!
لینک زمین بازی
پاسخ شماره ۲
مشکل این است که شیء صریح {fieldName: 'bar'} به عنوان نوع {fieldName: string} استنباط میشود، نه نوع خاصتر {fieldName: 'bar'}. هر کاری که با این شیء انجام دهید، مانند قرار دادن آن در یک آرایه و ارسال آن به یک تابع عمومی، نمیتواند نوع رشتهای اختصاصی 'bar' را از نوع آن بازیابی کند، زیرا آن رشته اختصاصی از ابتدا جزو نوع آن نبوده است.
یکی از راههای مقابله با این موضوع این است که شیء خود را با یک تابع عمومی بسازید به جای یک literal شیء، به طوری که نوع خاصتر ویژگی fieldName حفظ شود:
function makeObject<T>(s: T): { fieldName: T } {
return { fieldName: s };
}
const result = foo([ makeObject('bar') ])
// type error: Property 'baz' does not exist on type Record<'bar', undefined>
result.baz
لینک زمین بازی
