مقدمة: السحر المختبئ في الأنواع
\n\nتخيل أنك لا تكتب مجرد كود، بل مجموعة تعليمات للمُجمّع نفسه، والتي بناءً على قواعدك تُنشئ تركيبات آمنة وموثقة ذاتيًا. هذا هو البرمجة الفوقية في TypeScript — القدرة على كتابة كود يعمل على مستوى الأنواع، ويحلل ويحول الأنواع الأخرى أثناء التجميع.
\n\nيتوقف العديد من المطورين عند الأدوية العامة والواجهات الأساسية، دون أن يعلموا حتى ما هو الترسانة القوية المختبئة وراء الكلمات المفتاحية infer و extends والتركيبات الشرطية. في هذه المقالة، سنشرح التقنيات المتقدمة للبرمجة الفوقية في TypeScript التي ستحولك من مجرد مستخدم للغة إلى مهندسها الحقيقي.
1. الأنواع الشرطية: if على مستوى الأنواع
\n\nالأنواع الشرطية هي أساس أي برمجة على مستوى النوع. تعمل مثل العامل الثلاثي، ولكن على مستوى نظام الأنواع:
\n\ntype IsString<T> = T extends string ? "yes" : "no";\n\ntype Test1 = IsString<"hello">; // "yes"\ntype Test2 = IsString<42>; // "no"\n\n\nلكن السحر الحقيقي يبدأ عندما ندمجها مع الأدوية العامة. على سبيل المثال، لننشئ نوعًا يستخرج فقط مفاتيح السلسلة النصية من كائن:
\n\ntype StringKeys<T> = {\n [K in keyof T]: T[K] extends string ? K : never\n}[keyof T];\n\ninterface User {\n name: string;\n age: number;\n email: string;\n}\n\ntype UserStringKeys = StringKeys<User>; // "name" | "email"\n\n\nيوضح هذا المثال كيف تسمح الأنواع المتقدمة في TypeScript بتصفية وتحويل هياكل البيانات في مرحلة التجميع. أنت حرفيًا تبرمج منطقًا يتم تنفيذه قبل أن يصل الكود إلى وقت التشغيل.
\n\n2. Infer: استخراج الأنواع من أنواع أخرى
\n\nالكلمة المفتاحية infer هي، بلا شك، أقوى أداة في ترسانة البرمجة الفوقية في TypeScript. تسمح لك بـ "النظر داخل" النوع واستخراج أجزائه المكونة. لننظر إلى مثال كلاسيكي — الحصول على نوع القيمة المُعادة من دالة:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;\n\ntype Fn = (x: number) => string;\ntype FnReturn = ReturnType<Fn>; // string\n\n\nلكن infer يمكنها فعل المزيد. لنستخرج نوع الوسيط الأول لدالة:
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;\n\ntype Handler = (id: number, name: string) => void;\ntype HandlerFirstArg = FirstArg<Handler>; // number\n\n\nوماذا عن التحليل العودي للـ Promises؟ لننشئ نوعًا "يفك" جميع الـ Promise المتداخلة:
\n\ntype UnwrapPromise<T> = T extends Promise<infer U> ? UnwrapPromise<U> : T;\n\ntype DeepPromise = Promise<Promise<Promise<number>>>;\ntype Unwrapped = UnwrapPromise<DeepPromise>; // number\n\n\nلاحظ العودية — هذه واحدة من أكثر تقنيات TypeScript المتقدمة أناقة، والتي تسمح بمعالجة الهياكل المتداخلة بلا حدود.
\n\n3. Mapped types: تحويل الكائنات
\n\nMapped types تسمح بإنشاء أنواع جديدة بناءً على أنواع موجودة، عن طريق التكرار على مفاتيحها. هذه أداة لا غنى عنها لإنشاء أدوات مساعدة عامة:
\n\ntype Readonly<T> = {\n readonly [K in keyof T]: T[K];\n};\n\ntype Partial<T> = {\n [K in keyof T]?: T[K];\n};\n\n\nلكن يمكننا الذهاب أبعد من ذلك. لننشئ نوعًا يجعل جميع الخصائص اختيارية، ولكن فقط إذا كانت من نوع سلسلة نصية:
\n\ntype PartialStringProps<T> = {\n [K in keyof T]: T[K] extends string ? T[K] | undefined : T[K];\n};\n\ninterface Config {\n nam