مقدمة: ما هي Generics ولماذا هي مهمة؟
\n\nعند تعلم TypeScript، سرعان ما تواجه موقفًا تحتاج فيه إلى كتابة دالة أو كلاس يمكنه العمل مع أنواع بيانات مختلفة. بدون أدوات خاصة، سيكون عليك إما تكرار الكود لكل نوع، أو استخدام any، مما يقتل سحر التصنيف الصارم. هنا تأتي Generics (الجينيريكس أو التعميمات) للإنقاذ.
Generics هي آلية قوية في TypeScript تسمح لك بإنشاء مكونات (دوال، كلاسات، واجهات) تعمل مع أنواع مختلفة مع الحفاظ على التصنيف الصارم. تخيل أنك تكتب حاوية عالمية يمكنها تخزين التفاح والكتب والأرقام، ولكن TypeScript يعرف بالضبط ما بداخلها في كل حالة محددة.
\n\nفي هذه المقالة، سنشرح لماذا نحتاج إلى Generics، وكيفية تعريفها واستخدامها في المشاريع الحقيقية. ستتعلم كيفية جعل كودك أكثر مرونة وأمانًا وقابلية لإعادة الاستخدام.
\n\nأساسيات Generics: الصياغة وأول دالة
\n\nلنبدأ بمثال بسيط. لنفترض أننا نحتاج إلى دالة تأخذ وسيطًا وتعيده كما هو (دالة الهوية). بدون الجينيريكس، سنكتب هكذا:
\n\nfunction identity(arg: number): number {\n return arg;\n}\n\nلكن هذه الدالة تعمل فقط مع الأرقام. لجعلها عامة، يمكننا استخدام any:
function identity(arg: any): any {\n return arg;\n}\n\nالمشكلة هي أننا نفقد معلومات النوع. TypeScript لا يعرف أنه سيعيد نفس النوع الذي تم تمريره. هنا تأتي Generics للإنقاذ:
\n\nfunction identity<T>(arg: T): T {\n return arg;\n}\n\n// الاستخدام:\nlet output1 = identity<string>("مرحبا"); // النوع string\nlet output2 = identity<number>(42); // النوع number\n\nدعنا نحلل الصياغة:
\n- \n
<T>— هو معامل النوع. يمكنك استخدام أي حرف، ولكن حسب الاتفاقية الأكثر شيوعًا استخدامT(Type)،U،V. \n arg: T— الوسيط له النوعT. \n : Tبعد الأقواس — الدالة تعيد قيمة من النوعT. \n
الآن TypeScript يعرف: إذا قمت بتمرير نص، فسيكون الناتج نصًا. إذا كان رقمًا، فسيكون رقمًا. استنتاج الأنواع يعمل تلقائيًا في كثير من الأحيان، لذا يمكنك كتابة identity("مرحبا") فقط — سيفهم TypeScript أن T هو string.
Generics في الدوال: العمل مع المصفوفات والقيود
\n\nغالبًا ما تُستخدم الجينيريكس للعمل مع المصفوفات أو المجموعات. لننظر إلى دالة تعيد العنصر الأول من المصفوفة:
\n\nfunction getFirst<T>(arr: T[]): T | undefined {\n return arr[0];\n}\n\nconst numbers = [1, 2, 3];\nconst firstNumber = getFirst(numbers); // النوع number\n\nconst strings = ["a", "b", "c"];\nconst firstString = getFirst(strings); // النوع string\n\nلاحظ: T[] هي كتابة مختصرة لـ Array<T>. الدالة تعمل مع أي مصفوفة، ولكنها تعيد عنصرًا من نفس النوع المخزن في المصفوفة.
القيود (Constraints) باستخدام extends
\n\nأحيانًا تحتاج إلى أن يكون للنوع T خصائص معينة. على سبيل المثال، نريد الحصول على طول كائن. ليست كل الأنواع لها خاصية length، لذا يمكننا فرض قيود:
interface HasLength {\n length: number;\n}\n\nfunction getLength<T extends HasLength>(arg: T): number {\n return arg.length;\n}\n\ngetLength("نص"); // صحيح، النص له length\ngetLength([1, 2, 3]); // صحيح، المصفوفة لها length\ngetLength(42); // خطأ! الرقم ليس له length