الأنواع - الفرق بين توسيع وتقاطُع الواجهات في TypeScript؟
لنقل أن النوع التالي معرف:
interface Shape {
color: string;
}
الآن، ضع في اعتبارك الطرق التالية لإضافة خصائص إضافية لهذا النوع:
امتداد
interface Square extends Shape {
sideLength: number;
}
تقاطع
type Square = Shape & {
sideLength: number;
}
ما الفرق بين الطريقتين؟
ولأجل الاكتمال ومن باب الفضول، هل هناك طرق أخرى لتحقيق نتائج مماثلة؟
الإجابة رقم 1
نعم، هناك اختلافات قد تكون ذات صلة أو قد لا تكون ذات صلة في حالتك.
ربما يكون الأهم هو الفرق في كيفية التعامل مع الأعضاء الذين لديهم نفس مفتاح الخاصية عند وجودهم في كلا النوعين.
ضع في اعتبارك:
interface NumberToStringConverter {
convert: (value: number) => string;
}
interface BidirectionalStringNumberConverter extends NumberToStringConverter {
convert: (value: string) => number;
}
ينتج عن extends أعلاه خطأ لأن الواجهة المشتقة تعلن عن خاصية بنفس المفتاح الموجود في الواجهة الموروثة ولكن بتوقيع غير متوافق.
error TS2430: Interface 'BidirectionalStringNumberConverter' incorrectly extends interface 'NumberToStringConverter'.
Types of property 'convert' are incompatible.
Type '(value: string) => number' is not assignable to type '(value: number) => string'.
Types of parameters 'value' and 'value' are incompatible.
Type 'number' is not assignable to type 'string'.
ومع ذلك، إذا استخدمنا أنواع التقاطع
type NumberToStringConverter = {
convert: (value: number) => string;
}
type BidirectionalStringNumberConverter = NumberToStringConverter & {
convert: (value: string) => number;
}
لا يوجد أي خطأ على الإطلاق، وعلاوة على ذلك، هذا مفيد حقًا حيث يمكن تصور قيمة تتوافق مع هذا النوع التقاطعي المحدد بسهولة.
const converter: BidirectionalStringNumberConverter = {
convert: (value: string | number) => {
return (
typeof value === 'string'
? Number(value)
: String(value)
) as string & number; // type assertion is an unfortunately necessary hack.
}
}
لاحظ أن تنفيذ التقاطع، كما هو موضح أعلاه، ينطوي على بعض الأنواع والافتراضات المحرجة ولكن هذه مجرد آثار تنفيذية لا تساهم في نوع كائن المحول الذي يحدده فقط النوع BidirectionalStringNumberConverter المستخدم لتوضيح الكائن الحرفي converter.
const s: string = converter.convert(0); // `convert`'s call signature comes from `NumberToStringConverter`
const n: number = converter.convert('a'); // `convert`'s call signature comes from `BidirectionalStringNumberConverter`
رابط الملعب
فرق مهم آخر، إعلانات interface مفتوحة النهايات. يمكن إضافة أعضاء جدد في أي مكان لأن إعلانات interface المتعددة بنفس الاسم في نفس مساحة الإعلان يتم دمجها. هذا على النقيض تمامًا من التعبير النوعي الناتج عن & الذي ينشئ تعبيرًا مجهولًا يمكن ربطه باسم مستعار لإعادة الاستخدام ولكن لا يمكن تعزيزه عبر الدمج.
إليك استخدامًا شائعًا لسلوك الدمج
lib.d.ts
interface Array<T> {
// map, filter, etc.
}
array-flat-map-polyfill.ts
interface Array<T> {
flatMap<R>(f: (x: T) => R[]): R[];
}
if (typeof Array.prototype.flatMap !== 'function') {
Array.prototype.flatMap = function (f) {
// Implementation simplified for exposition.
return this.map(f).reduce((xs, ys) => [...xs, ...ys], []);
}
}
لاحظ كيف أنه لا توجد عبارة extends، على الرغم من أنه تم تحديدها في ملفات منفصلة، فإن الواجهات كلاهما في النطاق العالمي ويتم دمجها بالاسم في إعلان واجهة منطقي واحد يحتوي على كلا المجموعتين من الأعضاء. (يمكن القيام بالمثل لإعلانات النطاق الوحدوي مع اختلاف بسيط في الصياغة)
على النقيض من ذلك، تكون أنواع التقاطع، كما يتم تخزينها في إعلان type، مغلقة، وليست خاضعة للدمج.
هناك العديد والعديد من الاختلافات. يمكنك قراءة المزيد عن كل من البنى في دليل TypeScript. قسم أنواع الكائنات وقسم إنشاء الأنواع من الأنواع هما الأكثر صلة.
