البرمجة الفوقية في TypeScript: كيفية كتابة كود يكتب كودًا

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: السحر المختبئ في الأنواع

\n\n

تخيل أنك لا تكتب مجرد كود، بل مجموعة تعليمات للمُجمّع نفسه، والتي بناءً على قواعدك تُنشئ تركيبات آمنة وموثقة ذاتيًا. هذا هو البرمجة الفوقية في TypeScript — القدرة على كتابة كود يعمل على مستوى الأنواع، ويحلل ويحول الأنواع الأخرى أثناء التجميع.

\n\n

يتوقف العديد من المطورين عند الأدوية العامة والواجهات الأساسية، دون أن يعلموا حتى ما هو الترسانة القوية المختبئة وراء الكلمات المفتاحية infer و extends والتركيبات الشرطية. في هذه المقالة، سنشرح التقنيات المتقدمة للبرمجة الفوقية في TypeScript التي ستحولك من مجرد مستخدم للغة إلى مهندسها الحقيقي.

\n\n

1. الأنواع الشرطية: if على مستوى الأنواع

\n\n

الأنواع الشرطية هي أساس أي برمجة على مستوى النوع. تعمل مثل العامل الثلاثي، ولكن على مستوى نظام الأنواع:

\n\n
type IsString<T> = T extends string ? "yes" : "no";\n\ntype Test1 = IsString<"hello">;   // "yes"\ntype Test2 = IsString<42>;        // "no"\n
\n\n

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

\n\n
type 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\n

2. Infer: استخراج الأنواع من أنواع أخرى

\n\n

الكلمة المفتاحية infer هي، بلا شك، أقوى أداة في ترسانة البرمجة الفوقية في TypeScript. تسمح لك بـ "النظر داخل" النوع واستخراج أجزائه المكونة. لننظر إلى مثال كلاسيكي — الحصول على نوع القيمة المُعادة من دالة:

\n\n
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 يمكنها فعل المزيد. لنستخرج نوع الوسيط الأول لدالة:

\n\n
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\n
type 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\n

3. Mapped types: تحويل الكائنات

\n\n

Mapped types تسمح بإنشاء أنواع جديدة بناءً على أنواع موجودة، عن طريق التكرار على مفاتيحها. هذه أداة لا غنى عنها لإنشاء أدوات مساعدة عامة:

\n\n
type 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\n
type PartialStringProps<T> = {\n  [K in keyof T]: T[K] extends string ? T[K] | undefined : T[K];\n};\n\ninterface Config {\n  nam                

المدونات

توصيات الكتب