يحذف Exclude<T, U> أعضاء من نوع union، ويُبقي Extract<T, U> الأعضاء المطابقة فقط، ويحذف NonNullable<T> القيمتين null وundefined. الثلاثة كلها ترشّح unions؛ ولا تمس خصائص الكائنات.
مثل كل أنواع الأدوات، هذه موجودة وقت الترجمة فقط. يرفض المترجم السطر الأخير؛ أما JavaScript الذي يعمل فلا يعرف ما هو Visible.
Exclude وExtract وNonNullable بنظرة سريعة
| النوع | يُبقي | مثال | النتيجة |
|---|---|---|---|
Exclude<T, U> | أعضاء T غير القابلة للإسناد إلى U | Exclude<"a" | "b" | "c", "a"> | "b" | "c" |
Extract<T, U> | أعضاء T القابلة للإسناد إلى U | Extract<"a" | "b" | "c", "a" | "z"> | "a" |
NonNullable<T> | كل شيء عدا null وundefined | NonNullable<string | null | undefined> | string |
العبارة المفتاحية هي «قابل للإسناد إلى U». لا يلزم أن يكون U قائمة بأعضاء محددة؛ يمكن أن يكون نوعًا أوسع تناسبه عدة أعضاء:
اختيار أعضاء union حسب الشكل
على discriminated union، يستخرج Extract مع شكل جزئي المتغيرات التي تملكه. وهذه هي الطريقة المعتادة لتسمية متغير واحد من union لم تكتبه بنفسك:
الشكل { type: "click" } نوع أعلى (supertype) لمتغير click وحده، فهو ما يُبقيه Extract. أما الشكل الذي يطابق عدة متغيرات (مثل { x: number } لو كان في اثنين منها) فيُبقيها كلها.
NonNullable
NonNullable<T> هو نسخة الأنواع من فحص !== null && !== undefined. ويظهر عندما تشتق نوعًا من شيء اختياري:
User["manager"] هو { name: string } | null | undefined (علامة ? تضيف undefined)، ويزيل NonNullable الاثنين. ومنذ TypeScript 5.5 يستنتج المترجم هذا المسند (predicate) بنفسه، فيكون raw.filter((s) => s != null) من النوع string[] أصلًا؛ أما s is NonNullable<typeof s> الصريح فيُبقي النية واضحة ويعمل في الإصدارات الأقدم. تعريفه type NonNullable<T> = T & {}: نوع الكائن الفارغ {} يقبل كل قيمة عدا null وundefined، فالـ intersection معه يحذفهما بالضبط.
كيف تُبنى
Exclude وExtract نوعان شرطيان من سطر واحد في المكتبة القياسية:
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
عندما يكون T من نوع union، يُطبَّق النوع الشرطي على كل عضو منفصلًا (يتوزع)، وتختفي الأعضاء never من النتيجة. لذا يصبح Exclude<"a" | "b", "a"> هو ("a" extends "a" ? never : "a") | ("b" extends "a" ? never : "b")، أي never | "b"، أي "b". تشرح صفحة الأنواع الشرطية التوزيع بالتفصيل.
Exclude مقابل Omit، وExtract مقابل Pick
من السهل الخلط بين الأسماء لأن كلا الزوجين «يحذف» شيئًا أو «يُبقيه». والفرق فيما تعمل عليه:
| يعمل على | يحذف أو يُبقي | مثال | |
|---|---|---|---|
Exclude<T, U> | union | أعضاء union | Exclude<"a" | "b", "a"> → "b" |
Extract<T, U> | union | أعضاء union | Extract<"a" | "b", "a"> → "a" |
Omit<T, K> | نوع كائن | الخصائص | Omit<User, "password"> |
Pick<T, K> | نوع كائن | الخصائص | Pick<User, "name"> |
استخدام Exclude على نوع كائن زلّة شائعة، وتفشل بصمت:
Account غير قابل للإسناد إلى "password"، فيُبقيه Exclude كاملًا. وOmit نفسه مبني من Exclude مطبَّقًا على المفاتيح: Omit<T, K> هو Pick<T, Exclude<keyof T, K>>. راجع Omit للمزيد.
Exclude أكثر صرامة
يقبل المعامل الثاني لـ Exclude أي نوع، فالخطأ الإملائي لا يحذف شيئًا ولا يثير أي خطأ. إضافة قيد تجعل المترجم يتحقق من أن ما تستبعده موجود فعلًا في union:
index.ts(7,37): error TS2344: Type '"drafft"' does not satisfy the constraint 'Status'.
صحّح الإملاء فيُترجم الكود، ويكون Strict مساويًا لـ "published" | "archived". والحيلة نفسها تعمل مع Extract ومع Omit صارم (K extends keyof T).
الأسئلة الشائعة
ماذا يفعل Exclude في TypeScript؟
يحذف Exclude<T, U> من union T كل عضو قابل للإسناد إلى U. فـ Exclude<"a" | "b" | "c", "a"> هو "b" | "c". ولحذف عدة أعضاء مرّر union كـ U: Exclude<T, "a" | "b">.
ما الفرق بين Exclude وExtract؟
هما متعاكسان. يُبقي Exclude<T, U> أعضاء T غير القابلة للإسناد إلى U؛ ويُبقي Extract<T, U> الأعضاء القابلة لذلك فقط. ومع T وU نفسيهما، تكوّن النتيجتان معًا T.
ما الفرق بين Exclude وOmit؟
يعمل Exclude على نوع union ويحذف أعضاءه. ويعمل Omit على نوع كائن ويحذف خصائص. Exclude<User, "password"> لا يفعل شيئًا مفيدًا (يعيد User)، بينما يعطي Omit<User, "password"> نوع الكائن دون تلك الخاصية. وOmit مبني على Exclude: يستبعد المفتاح من keyof T.
ماذا يفعل NonNullable في TypeScript؟
يحذف NonNullable<T> القيمتين null وundefined من نوع: NonNullable<string | null | undefined> هو string. وهو معرّف كـ T & {}، لأن {} يقبل كل قيمة عدا null وundefined.
لماذا لا يبلّغ Exclude عن خطأ لعضو غير موجود؟
معامله الثاني غير مقيّد، فيُقبل أي نوع، وخطأ إملائي مثل Exclude<Status, "drafft"> لا يحذف شيئًا بصمت. عرّف نسخة أكثر صرامة، type StrictExclude<T, U extends T> = Exclude<T, U>، ليتحقق المترجم من أن U جزء من T.