Menu
flag Ar iconالعربيةdown icon

أنواع Intersection في TypeScript: دمج الأنواع باستخدام &

نوع intersection مثل A & B يصف قيمة هي A وB في الوقت نفسه، فتملك كل أعضاء الاثنين. تعرّف على دمج أنواع الكائنات بـ &، ولماذا تصبح الخصائص المتعارضة never، وكيف تحتفظ intersection بين أنواع union بالأعضاء المشتركة فقط، ومتى تستخدم extends بدلًا منها.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

نوع intersection، ويُكتب A & B، يصف قيمة هي A وB في الوقت نفسه. في أنواع الكائنات يعني ذلك أن القيمة تملك كل خصائص الاثنين. وهكذا تدمج أنواعًا موجودة دون كتابة خصائصها من جديد.

يترك الكائن الثاني team، فيكون خطأ الترجمة TS2322، ويقول السطر التالي من الرسالة Property 'team' is missing ... but required in type 'Employee'. ويمكن تمرير قيمة Staff في أي مكان يُتوقع فيه Person أو Employee.

دمج أنواع الكائنات

يعمل & مع أي مزيج من type aliases والواجهات وأنواع الكائنات المضمّنة، ومع معاملات الأنواع العامة. والحالة الأخيرة هي حيث يصعب استبداله: الدالة التي تضيف خصائص إلى أي كائن تستقبله يمكنها أن تقول ذلك بدقة.

يحتفظ المستدعي بالنوع الدقيق لما مرره (title وwords) إضافة إلى الخاصيتين المضافتين. لا يستطيع interface ... extends التعبير عن «أيًا كان T، مع هذه الخصائص»، لأن الواجهة لا تستطيع توسيع معامل نوع.

الخصائص المتعارضة تصبح never

عندما يعلن الطرفان الخاصية نفسها، يكون نوع الخاصية هو intersection النوعين. وإذا لم تكن لهذين النوعين قيمة مشتركة، تصبح الخاصية never، ولا يقول المترجم شيئًا حتى تحاول إنشاء قيمة:

index.ts(7,22): error TS2322: Type 'string' is not assignable to type 'never'.

يشير الخطأ إلى الكائن، لا إلى النوع الذي تسبب فيه، ما يجعل تتبع هذه الأخطاء بطيئًا. وتمرير المؤشر فوق r.id في المحرر يُظهر نوعها: never. وعندما تكون الخاصية المتعارضة وسمًا حرفيًا، كما في type Shape = { kind: "circle" } & { kind: "square" }، تذهب TypeScript أبعد وتختزل الـ intersection كله إلى never. عندها تشرح قراءة خاصية من قيمة كهذه السبب: Property 'kind' does not exist on type 'never'. The intersection 'Shape' was reduced to 'never' because property 'kind' has conflicting types in some constituents.

أما interface ... extends فيكتشف التعارض نفسه عند التصريح، بالخطأ TS2430. وهذا هو الفرق العملي الرئيسي بين الاثنين؛ وتقارن صفحة interface مقابل type بينهما جنبًا إلى جنب.

التداخل المتوافق يضيّق الخاصية

إذا كان نوعا الخاصية يتداخلان فعلًا، يكون الناتج هو التداخل. هذا مفيد وليس خطأ:

يُظهر النصف الثاني ما يفعله & بأنواع union: يحتفظ بالأعضاء المشتركة بين الطرفين. التفكير في الأنواع كمجموعات من القيم يجعل هذا متوقعًا. A | B اتحاد المجموعتين، وA & B تقاطعهما، والتقاطع الخالي هو never.

Intersection مقابل Union

الاسمان مأخوذان من نظرية المجموعات، ويبدوان معكوسين عند تطبيقهما على خصائص الكائنات:

A | B (union)A & B (intersection)
القيمة هيA أو BA وB
مجموعة القيم المسموحةأكبرأصغر
الخصائص التي يمكنك استخدامهاالموجودة في الاثنين فقطكل ما في أي منهما
string مع numberstring | numbernever
"a" | "b" مع "b" | "c""a" | "b" | "c""b"

يملك intersection أنواع الكائنات خصائص أكثر لأنه تحديدًا يسمح بقيم أقل: الكائنات التي تملك كل شيء فقط.

Intersection مقابل extends

type C = A & Binterface C extends A, B
يعمل معأي أنواع، ومنها unions ومعاملات الأنواعأنواع كائنات أعضاؤها معروفة ثابتًا
الخاصية المتعارضةتصبح never بصمتالخطأ TS2430 أو TS2320 عند التصريح
الناتجintersection يُفحص مكوّنًا بعد مكوّننوع مسمى واحد مسطح تُخزَّن علاقاته مؤقتًا
التركيبات الكبيرةقد تبطئ فحص الأنواعتفضّله صفحة الأداء في wiki الخاص بـ TypeScript

لدمج بضعة type aliases لأنواع كائنات، يكون & أسلوبًا مألوفًا ومناسبًا. ولنوع مبني من أجزاء كثيرة، أو لنوع في واجهة برمجية عامة، يعطي extends أخطاء أبكر وفحص أنواع أقل تكلفة.

Intersection مع الأنواع البدائية: الوسم (Branding)

تقاطع نوع بدائي مع نوع كائن لا ينتج never: string & { readonly __brand: "UserId" } نص يحمل علامة إضافية موجودة وقت الترجمة فقط. لا يملك أي نص حقيقي تلك الخاصية، وهذا تحديدًا هو المقصود: لا يستطيع إنشاء قيمة كهذه إلا كود يؤكد الوسم عمدًا، فلا يعود ممكنًا تمرير string عادي أو OrderId حيث يُتوقع UserId. ولهذه التقنية صفحتها الخاصة، branded types.

الأسئلة الشائعة

ما هو نوع intersection في TypeScript؟

نوع يُكتب A & B ويجب أن تحقق قيمه A وB في الوقت نفسه. في أنواع الكائنات يعني ذلك أن القيمة تملك كل خصائص A وكل خصائص B. وهو الطريقة المعتادة لدمج type aliases اثنين في واحد.

ما الفرق بين union و intersection؟

الـ union A | B يعني «أحدهما»: القيمة يمكن أن تكون A أو B، ولا تستطيع استخدام إلا ما يشتركان فيه حتى تضيّق. والـ intersection A & B يعني «كلاهما»: القيمة تملك كل شيء من الاثنين. ومع أنواع الكائنات، يقبل الـ union قيمًا أكثر ويملك الـ intersection خصائص أكثر.

لماذا أصبح نوع intersection لدي never؟

لأن لا قيمة تستطيع تحقيق الطرفين. string & number هو never، و{ id: string } & { id: number } يجعل id من نوع string & number، فتصبح الخاصية never ولا يمكن إنشاء أي كائن. وإذا كان لنوعي كائن الوسم الحرفي نفسه بقيمتين مختلفتين (kind: "circle" وkind: "square")، يُختزل الـ intersection كله إلى never.

هل أستخدم intersection أم extends؟

كلاهما يدمج أنواع الكائنات. يبلّغ interface X extends A, B عن الخصائص المتعارضة عند التصريح، ويوصي به فريق TypeScript لتركيب أنواع الكائنات الكبيرة. ويعمل & مع أي نوع، ومنها unions ومعاملات الأنواع العامة، وهو ما لا يستطيع extends دمجه. استخدم & مع type aliases والدوال المساعدة العامة، وextends عند بناء الواجهات.

كيف أدمج نوعي كائن في TypeScript؟

اكتب type Merged = A & B. وللقيمة وقت التشغيل، انشر الكائنين: const merged: A & B = { ...a, ...b }. إذا اشترك A وB في خاصية بنوعين مختلفين، يصبح النوع never لتلك الخاصية؛ استخدم Omit<A, keyof B> & B عندما يجب أن تحل خصائص الكائن الثاني محل خصائص الأول.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن