أتقِن توزيعية الأنواع الشرطية، ومطابقة الأنماط بواسطة infer، والحالات الحدّية مثل never وany وboolean.
افتح هذا الدرس في Kodokonالأنواع الشرطية هي آلية التفرّع في نظام الأنواع: T extends U ? X : Y تسأل المُصرِّف عمّا إذا كان T قابلاً للإسناد إلى U، ثم تختار فرعاً. جميع الأدوات القياسية مثل ReturnType أو Exclude أو Awaited مبنيّة عليها. في هذا المستوى، لم يعد السؤال كيف تكتبها، بل كيف تتوقّع بدقّة الطريقة التي يُقيِّمها بها المُصرِّف، خصوصاً عندما تتدخّل الاتّحادات وnever.
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">;
// true
type B = IsString<string | number>;
// boolean, not false!السلوك الأساسي هو التوزيعية: عندما يكون النوع المُختبَر بارامتر نوع مجرّداً (غير ملفوف داخل صف tuple أو مصفوفة أو تقاطع)، يُطبَّق الشرط على كل عضو من أعضاء الاتّحاد على حدة، ثم تُجمَع النتائج. لذلك يحسب IsString<string | number> القيمة true | false، أي boolean. ولتعطيل هذا التوزيع، لُفّ الطرفين داخل صف tuple: [T] extends [U]. حالة حدّية مخيفة: never هو الاتّحاد الفارغ؛ التوزيع على صفر أعضاء لا يُقيِّم أي فرع إطلاقاً، وتكون النتيجة never أياً كان الفرع الذي كتبته.
type ToArray<T> = T extends unknown ? T[] : never;
type NoDist<T> = [T] extends [unknown] ? T[] : never;
type A = ToArray<string | number>;
// string[] | number[]
type B = NoDist<string | number>;
// (string | number)[]
type IsNever<T> = [T] extends [never] ? true : false;
type C = IsNever<never>; // true thanks to the tupleتُدخِل الكلمة المفتاحية infer متغيّر نوع يُلتقَط عبر مطابقة الأنماط: يحاول المُصرِّف مطابقة بنية النوع المُختبَر مع النمط، ويربط المتغيّر إذا نجحت المطابقة. يمكنك وضع عدّة تصريحات infer بالاسم نفسه: في موضع تغايري مشترك (قيم مُنتَجة، مثل نوع الإرجاع)، تُدمَج المُرشّحات في اتّحاد؛ وفي موضع تغايري معاكس (بارامترات دالّة)، تُدمَج في تقاطع. تنبثق هذه القاعدة مباشرة من التغايرية: إنها الخيار الآمن الوحيد في كل اتّجاه.
type ElementOf<T> =
T extends readonly (infer E)[] ? E : never;
type FirstParam<T> =
T extends (arg: infer P) => unknown ? P : never;
type Merged<T> =
T extends { a: (x: infer P) => void;
b: (x: infer P) => void }
? P : never;
type M = Merged<{
a: (x: { id: string }) => void;
b: (x: { tag: string }) => void;
}>; // { id: string } & { tag: string }منذ TypeScript 4.7، تقبل infer قيداً مضمّناً: infer N extends number يُرشِّح الالتقاط، ومنذ الإصدار 4.8 يحوّل المُصرِّف حتى سلسلة رقمية حرفية إلى عدد حرفي. دقّة أخرى في المواصفات: يبقى النوع الشرطي الذي ما يزال مُدخَله يحتوي بارامتراً عامّاً غير محلول مؤجَّلاً؛ ولا يُقيَّم إلا في موضع التنصيب، وهذا ما يفسّر لماذا تظهر بعض الأخطاء فقط من جانب المُستدعي.
type ToNumber<S extends string> =
S extends `${infer N extends number}` ? N : never;
type Port = ToNumber<"8080">; // the literal 8080
type Bad = ToNumber<"abc">; // neverExclude<boolean, true>؟type IsNever<T> = T extends never ? true : false;، ما هو IsNever<never>؟infer P بالاسم نفسه موضوعان في موضع بارامتر دالّة يُنتجان: