نوع تقاطع تایپاسکریپت و امضای تابع خطای مورد انتظار را ایجاد نمیکند
من انواع زیر را اعلام کردهام:
type ExampleA = {
a: string;
}
type ExampleB = {
b: number;
}
type ExampleC = {
c: boolean;
}
type Examples = ExampleA &
ExampleB &
ExampleC;
سپس من نوع را به صورت زیر استفاده میکنم:
function foo(pattern: { [key: string]: string }) {
console.log(pattern);
}
const bar: Examples = { a: 'foo', b: 1, c: false };
foo(bar);
کامپایلر تایپاسکریپت هیچ خطایی در فراخوانی متد foo(bar) نشان نمیدهد، حتی با اینکه متغیر bar:Examples با امضای تابع foo مطابقت ندارد.
لینک زمین بازی
چرا تایپاسکریپت هیچ خطایی نمیدهد؟ آیا این یک باگ در کامپایلر است؟
پاسخ شماره ۱
دلیل اینکه این کار میکند، قابلیت انتساب یک نوع اشتراکی به نوعهای پایه آن است.
به عنوان یک نوع تقاطعی، Examples قابل انتساب به ExampleA است. ExampleA قابل انتساب به { [key: string]: string } است. بنابراین، Examples باید قابل انتساب به نوع پارامتر تابع باشد.
این را میتوان در این کد نشان داد:
const bar: Examples = { a: 'foo', b: 1, c: false };
const bar2: ExampleA = bar;
const bar3: { [key: string]: string } = bar2;
foo(bar3); //This works
foo(bar2); //Since the assignment bar3 = bar2 works, this must work, too
foo(bar); //Since the assignment bar2 = bar works, this must work, too
نسخهٔ زمین بازی
بهروزرسانی
رفتار زمانی مهم است که بخواهید اصل «وقتی A قابل انتساب به B است و B قابل انتساب به C است، آنگاه A باید قابل انتساب به C باشد» را رعایت کنید. سیستم نوع هیچ گزینه دیگری جز اجازه دادن به این نوع انتسابها ندارد. با این حال، در واقع یک مسئله دیگر در ارسال مقدار به عنوان پارامتر به foo وجود دارد.
شما میتوانید یک مقدار را به متغیری از نوعی که تنها بخشی از اعضای مقدار اختصاص داده شده را به اشتراک میگذارد، اختصاص دهید. بنابراین این انتساب به خوبی کار میکند:
let item: { a: string, b: number } = { a: "Hello World!", b: 1 };
let partiallyMatchingItem: { a: string } = item;
کاملاً مشکلی ندارد که partiallyMatchingItem خصوصیات بیشتری نسبت به آنچه در نوع اعلام شده است داشته باشد. تضمین یک تضمین حداقلی است.
با این حال، اختصاص به یک نوع نگاشته شده کار نمیکند، زیرا عضو اضافی item از نوع number:
let item = { a: "Hello World!", b: 1 };
let mappedTypeItem: { [key: string]: string } = item; //Error
پس این بار تضمین یک تضمین حداقلی نیست، بلکه یک تضمین مطلق است. و این کاملاً مضحک است، وقتی در نظر بگیرید که چقدر به راحتی میتوان از آن عبور کرد (عمداً یا سهواً):
let item = { a: "Hello World!", b: 1 };
let partiallyMatchingItem: { a: string } = item;
let mappedTypeItem: { [key: string]: string } = partiallyMatchingItem;
یا به سادگی:
let item = { a: "Hello World!", b: 1 };
let mappedTypeItem: { [key: string]: string } = item as { a: string };
این یک خطا است که در انتظار وقوع است، به ویژه زمانی که خواص mappedTypeItem را شمارهگذاری میکنید و فرض میکنید که مقادیر تمام خواص یک string هستند.
با توجه به اینکه انتسابهای با نوع ساختاری در تایپاسکریپت بسیار رایج هستند، این تضمین مطلق با سیستم حداقل تضمینهایی که به طور کلی توسط سیستم نوع ارائه میشوند، سازگار نیست.
راهحل تمیز این است که مقادیر نوعهای «معمولی» قابل انتساب به نوعهای نگاشتهشده نباشند (اگر سازگاری با نسخههای قبلی لازم است، میتوانید آن را با یک سوئیچ در فایل tsconfig.json تغییر دهید). حداقل، احتمالاً باید از این نوع انتسابها اجتناب کنید زیرا ایمنی نوعی که اینجا فراهم شده است نسبتاً ضعیف است.
پاسخ شماره ۲
اگر واقعاً میخواهید خطا بگیرید، میتوانید Example را به عنوان اینترفیس اعلام کنید، نه نوع تلاقی. از نسخه ۲.۲، اینترفیس میتواند نوع شیء (یا حتی نوع تلاقی) را گسترش دهد.
type ExampleA = {
a: string;
}
type ExampleB = {
b: number;
}
type ExampleC = {
c: boolean;
}
interface Examples extends ExampleA, ExampleB, ExampleC {
}
function foo(pattern: { [key: string]: string }) {
console.log(pattern);
}
const bar: Examples = { a: 'foo', b: 1, c: false };
foo(bar); // error
یا حتی به این شکل، برای نشان دادن بهتر تفاوت بین نوعهای رابط و نوعهای تقاطع:
type Examples = ExampleA & // the same as in question
ExampleB &
ExampleC;
interface IExamples extends Examples { // empty interface "collapses" the intersection
}
const bar1: Examples = { a: 'foo', b: 1, c: false };
foo(bar1); // no error
const bar2: IExamples = { a: 'foo', b: 1, c: false };
foo(bar2); // error
یک روش دیگر برای ساخت نوع شیء از تقاطع، همانطور که تیسیان در نظر خود پیشنهاد داد، استفاده از نوع نگاشته شده است که تقریباً، اما نه کاملاً، با پارامتر عمومی خود یکسان است:
type Id<T> = { [P in keyof T]: T[P] }
const bar3: Id<ExampleA & ExampleB & ExampleC> = { a: 'foo', b: 1, c: false };
foo(bar3); // error
