تخطي إلى المحتوى الرئيسي

الأنواع العامة (Generics) – الجزء الأول: الدوال والواجهات typescript

الأنواع العامة (Generics) – الجزء الأول: الدوال والواجهات

دورة Typescript من الصفر الى الاحتراف باللغة العربية – الدرس #19

مقدمة عن الأنواع العامة (Generics)

في هذا الدرس، سنتناول مفهوم الأنواع العامة (Generics) وأهميتها في لغة Typescript وكيفية استخدامها لتحسين إعادة استخدام الكود وتقليل الأخطاء.

تعريف الأنواع العامة

الأنواع العامة هي ميزة قوية في Typescript تسمح لك بتعريف الدوال والواجهات التي يمكن إعادة استخدامها دون تحديد نوع معين.

  • الدوال العامة: هي دوال يمكن تعريفها بنوع عام لتحسين إعادة استخدام الكود وتقليل الأخطاء الناتجة عن تحديد النوع الخاطئ.
  • الواجهات العامة: هي واجهات يمكن تعريفها بنوع عام لتمكين إعادة استخدام الواجهات وتحسين وضوح الكود.

لماذا نحتاج إلى الأنواع العامة؟

تعاني لغات البرمجة مثل JavaScript من مشكلة تحديد نوع المتغيرات والوظائف بشكل صريح، مما يؤدي إلى زيادة حجم الأكواد وزيادة احتمالية الأخطاء البرمجية.

  • زيادة حجم الكود:
  • 
    let myNumber: number = 10;
    let myString: string = "مرحبا بالعالم";
    
    // زيادة حجم الكود بتعريف النوع بشكل صريح
    function myFunction(number: number, string: string): void {
      console.log(number);
      console.log(string);
    }
    
    myFunction(10, "مرحبا بالعالم");
    		
  • تزايد الأخطاء البرمجية:
  • 
    function myFunction(number: number): void {
      let myString: string = "مرحبا بالعالم"; // خطأ: لا يمكن تعيين نوع السلسلة لمتغير من نوع الرقم
    }
    
    myFunction(10);
    		
    • تعريف الأنواع العامة يساعد في تحسين إعادة استخدام الكود وتقليل الأخطاء البرمجية.

    دروس رئيسية حول الأنواع العامة

    الدوال العامة

    في هذا القسم، سنتعلم كيفية تعريف الدوال العامة وكيفية استخدامها.

    الواجهات العامة

    في هذا القسم، سنتعلم كيفية تعريف الواجهات العامة واستخدامها.

    أمثلة عملية

    في هذا القسم، سنقدم لك بعض الأمثلة العملية على استخدام الأنواع العامة لتعزيز فهمك للمفاهيم المقدمة.

    الملخص

    في هذا القسم، سنلخص المفاهيم الرئيسية التي تعلمناها عن الأنواع العامة.

    دورة Typescrip – الجزء الثاني: أنواع الدوال العامة (Generic Functions)

    دورة Typescrip – الجزء الثاني: أنواع الدوال العامة (Generic Functions)

    مقدمة عن الدوال العامة

    في هذا القسم، سنتناول مفهوم الدوال العامة في لغة TypeScript وكيفية تعريف دوال تقبل أنواع مختلفة من الوسائط والقيم العائدة. تعتبر الدوال العامة جزءاً أساسياً في TypeScript وتسمح بكتابة دوال يمكن استدعاؤها مع أنواع مختلفة من الوسائط والقيم العائدة دون الحاجة لتحديد الأنواع بشكل صريح لكل استدعاء للدالة.

    تعريف الدوال العامة

    لإعلان دالة عامة في TypeScript، نستخدم الكلمة المفتاحية “generic” قبل اسم الدالة. على سبيل المثال:

    
    	function generalFunction(): T {
    		// الكود هنا
    	}
    	

    في المثال أعلاه، قمنا بتعريف دالة عامة باسم “generalFunction” تقبل معامل من نوع غير محدد وتعيد القيمة من نفس النوع. يمكن استخدام هذه الدالة مع أنواع مختلفة من الوسائط والقيم العائدة حسب الحاجة.

    استخدام الدوال العامة

    عند استخدام دالة عامة، نستخدم أقواس {} لتحديد النوع الخاص بالمعامل <T>. على سبيل المثال:

    
    	const result: string = generalFunction(3);
    	

    في المثال السابق، قمنا بتعريف معامل من نوع T داخل الأقواس واستخدمناه لتقديم القيمة 3 كوسيط للدالة generalFunction(). سيتم تعيين النتيجة إلى المتغير result من نوع string.

    أنواع الوسائط والقيم العائدة

    في الدوال العامة، يمكننا تحديد أنواع الوسائط والقيم العائدة باستخدام الأنواع البدائيةnumber, string, boolean, any.

    على سبيل المثال:

    
    	interface MyInterface {
    		method1: T;
    		method2: T;
    	}
    	
    	function genericFunction<T>(): T {
    		return 'Hello World!' + T;
    	}
    	
    	const myObject: MyInterface<string> = {
    		method1: '',
    		method2: ''
    	};
    	
    	const result: string = genericFunction(myObject.method1);
    	

    في المثال السابق، نعرف واجهة MyInterface بنوع T. بعد ذلك، نعرف دالة عامة genericFunction بنوع <T>. نستخدم هذه الدوال لإنشاء كائن من نوع MyInterface بنوع string وتعيين نتيجة استدعاء genericFunction إلى متغير من نوع string.

    استخدام الأنواع البدائية

    في هذا القسم، سنقدم شرحاً تفصيلياً حول كيفية استخدام الأنواع البدائية number, string, boolean, any. سنوضح كيفية تعريف دوال عامة تأخذ أنواع البدائية كوسيط وتعيد قيم من نفس النوع.

    استخدام الدوال العامة

    في هذا القسم، سوف نقدم مثالاً عملياً عن كيفية استخدام الدوال العامة في TypeScript.

    استخدام الواجهات العامة

    في هذا القسم، سنتناول كيفية تعريف واجهات عامة في TypeScript وكيفية استخدام الأنواع البدائية في تعريف الواجهات.

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

    في هذا القسم، سنشرح كيفية استخدام الأنواع البدائية مثل number, string, boolean, any في الدوال العامة.

    استخدام الدوال العامة مع الواجهات

    في هذا القسم، سنشرح كيفية استخدام الدوال العامة مع الواجهات وتحديد الأنواع البدائية مثل number, string, boolean, any.

    استخدام الدوال العامة مع الدوال الأخرى

    في هذا القسم، سنشرح كيفية استخدام الدوال العامة مع الدوال الأخرى وتحديد الأنواع البدائية مثل number, string, boolean, any.

    استخدام الدوال العامة في الأمثلة العملية

    في هذا القسم، سنقدم أمثلة عملية توضح استخدام الدوال العامة وتحديد الأنواع البدائية مثل number, string, boolean, any.

    في هذا الدرس، سنستعرض مفهوم الدوال العامة وأنواع الدوال البدائية التي يمكن استخدامها في تعريف الدوال العامة وفي الواجهات وتحديد القيم البدائية مثل number, string, boolean, any. سنناقش أيضاً استخدام الدوال العامة مع دوال أخرى والواجهات في TypeScript.

    ستشمل الأمثلة العملية أنواع البدائية number, string, boolean, any. سنوضح كيف يمكن استخدام هذه الأنواع البدائية في تعريف الدوال العامة والواجهات وفي الأمثلة العملية.

    استنتاج

    في هذا القسم، سنختصر المفاهيم الأساسية التي تم شرحها في الدرس ونربطها ببعضها البعض.

    ستتضمن الأمثلة العملية الأنواع البدائية number, string, boolean, any. سنوضح كيفية استخدام الدوال العامة والواجهات مع هذه الأنواع البدائية.

    في هذا الدرس، سنتناول مفهوم الدوال العامة وأنواع الدوال البدائية التي يمكن استخدامها في تعريف الدوال العامة والواجهات وتحديد القيم البدائية مثل number, string, boolean, any. سنشرح كيفية استخدام هذه الأنواع البدائية في الأمثلة العملية والتعامل معها داخل الدوال العامة والواجهات.

    واجهات عامة (Generic Interfaces) في TypeScript

    واجهات عامة (Generic Interfaces) في TypeScript

    مقدمة

    في الأجزاء السابقة من هذه الدورة تعلمنا عن الأنواع العامة (Generic Types) والوظائف العامة (Generic Functions). في هذا القسم، سنتعرف على الواجهات العامة (Generic Interfaces) وكيف يمكننا استخدامها لتعريف واجهات عامة وتحديد الخصائص والطرق التي يمكن أن تحتويها هذه الواجهات.

    تعريف الواجهات العامة

    الواجهة العامة هي نوع خاص من الواجهات في TypeScript يسمح لنا بتعريف واجهة عامة باستخدام الكلمة الأساسية “generic”. على سبيل المثال:

    
    	interface IGenericInterface {
    		// خصائص وطرق الواجهة العامة
    	}
    	

    في هذا المثال، قمنا بتعريف واجهة عامة تسمى “IGenericInterface” بنوع عام “T”. يمكننا استخدام هذا النوع العام لتحديد الخصائص والطرق التي ستتعامل مع النوع “T”.

    تعريف واجهة عامة مع خصائص وطرق عامة

    كما ذكرنا سابقاً، الواجهات العامة تسمح لنا بتعريف واجهة عامة مع خصائص وطرق عامة تتعامل مع النوع العام “T”. سنقوم الآن بشرح كيفية تعريف واجهة عامة مع خصائص وطرق عامة:

    
    	interface IGenericInterface {
    		// خصائص عامة تتعامل مع النوع T
    		public method1(arg1: T): void;
    		public method2(arg1: T[]): void;
    		
    		// طرق عامة تتعامل مع النوع T
    		public property1: T;
    		public property2: T[];
    	}
    	

    في هذا المثال، قمنا بتعريف واجهة عامة تسمى “IGenericInterface” بنوع عام “T”. تحتوي هذه الواجهة على خصائص عامة (public properties) وطرق عامة (public methods) تتعامل مع النوع العام “T”. الخصائص العامة هي “method1” و”method2″، بينما الطرق العامة هي “method1″ و”method2”. سنشرح بالتفصيل كيفية استخدام هذه الواجهات العامة في الدروس التالية.

    استخدام الواجهات العامة

    في هذا القسم، سنشرح كيفية استخدام الواجهات العامة التي قمنا بتعريفها في الدرس السابق.

    مثال عملي

    لنفترض أننا نريد تعريف واجهة عامة للتعامل مع مجموعة من الأعداد الصحيحة.

    
    	interface IGenericInterface {
    		public method1(arg1: T): void;
    		public method2(arg1: T[]): void;
    		public property1: T;
    		public property2: T[];
    	}
    	
    	class MyClass implements IGenericInterface {
    		public method1(arg1: number): void {
    			// التنفيذ الخاص بشركتنا للأرقام
    		}
    		
    		public method2(arg1: number[]: void {
    			// التنفيذ الخاص بشركتنا للمصفوفات العددية
    		}
    		
    		public property1: number;
    		public property2: number[];
    	}
    	

    في هذا المثال العملي، قمنا بتعريف واجهة عامة تسمى “IGenericInterface” بتعريف الخصائص “property1″ و”property2” والطرق “method1″ و”method2”. لقد قمنا أيضاً بتعريف صنف “MyClass” الذي يطبق واجهة “IGenericInterface” بنوع “number”. سنشرح كيفية التعامل مع الأعداد الصحيحة في التنفيذ الخاص بالشركة.

    استخدام الواجهات العامة في برمجتك

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

    استخدام الواجهات العامة في برمجتك

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

    استخدام الواجهات العامة في برمجتك

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

    مثال عملي

    لنفترض أننا نريد تعريف واجهة عامة للتعامل مع الأعداد الصحيحة وتطبيقها على صنف معين.

    فهم الواجهات العامة في برمجتك

    بعد استكشاف الأنواع العامة والتعريفات البرمجية السابقة، يمكنك تطبيق هذه المعرفة في برمجتك الخاصة للتعامل مع الأنواع المرغوبة. يمكنك تعريف واجهات عامة خاصة بمشروعك وتطبيقها على الأصناف المناسبة.

    استخدام الأنواع العامة (Using Generic Types)

    بعد أن تعرفنا في الأقسام السابقة على المفهوم الأساسي للأنواع العامة (Generics) وكيفية تعريفها في الدوال والواجهات، ننتقل الآن إلى الجانب العملي والحيوي: كيفية استخدام هذه الأنواع العامة بفعالية في تطبيقاتنا. إن فهم كيفية تطبيق الأنواع العامة ليس مجرد معرفة نظرية، بل هو مهارة جوهرية تمكن المطورين من كتابة كود TypeScript أكثر قوة، مرونة، وأقل عرضة للأخطاء. تهدف هذه الأنواع إلى توفير طبقة إضافية من الأمان والوضوح في الشيفرة، مما يقلل من الحاجة إلى تكرار الكود (DRY principle) ويحسن من قابلية الصيانة على المدى الطويل. سنتعمق في الأمثلة العملية ونستكشف كيف يمكن للأنواع العامة أن تحدث فرقًا حقيقيًا في جودة مشروعك البرمجي، سواء كنت تتعامل مع هياكل بيانات معقدة أو وظائف مساعدة بسيطة.

    تطبيق الأنواع العامة في الدوال (Applying Generics in Functions)

    تُعد الدوال العامة حجر الزاوية في بناء مكتبات وأدوات مساعدة قابلة لإعادة الاستخدام في TypeScript. يكمن جمال استخدام الأنواع العامة في الدوال في قدرتها على العمل مع مجموعة واسعة من أنواع البيانات دون التضحية بالسلامة النوعية. بدلاً من كتابة دالة منفصلة لكل نوع بيانات محتمل، تسمح لنا الأنواع العامة بإنشاء دالة واحدة متعددة الاستخدامات يمكنها التكيف مع النوع المحدد الذي يتم تمريره إليها في وقت الاستدعاء. هذا لا يقلل فقط من حجم الكود، بل يعزز أيضًا الاتساق ويجعل الكود أسهل في القراءة والفهم. لنأخذ مثالاً لدالة تقوم بتصفية مصفوفة بناءً على شرط معين. بدون الأنواع العامة، قد نضطر لاستخدام النوع any، مما يهدم فائدة TypeScript، أو نكتب دالات متكررة لأنواع مختلفة.

    عند استخدام دالة عامة، يقوم TypeScript عادةً باستنتاج النوع العام (Type Inference) تلقائيًا بناءً على الوسائط التي يتم تمريرها إلى الدالة. هذا يعني أنك غالبًا لا تحتاج إلى تحديد النوع العام بشكل صريح، مما يجعل الكود أكثر إيجازًا ووضوحًا. ومع ذلك، في بعض السيناريوهات المعقدة أو عندما تحتاج إلى توجيه TypeScript، يمكنك تحديد النوع العام يدوياً. على سبيل المثال، إذا كانت لديك دالة تقوم بدمج مصفوفتين، فإن تحديد النوع العام يضمن أن المخرجات ستكون من نفس النوع المتوقع، مما يمنع الأخطاء الشائعة مثل محاولة الوصول إلى خصائص غير موجودة على الكائنات الناتجة. هذا المستوى من الدقة في الأنواع هو ما يميز الأنواع العامة ويجعلها أداة لا غنى عنها للمطورين المحترفين.

    function filterArray<T>(items: T[], predicate: (item: T) => boolean): T[] {
      return items.filter(predicate);
    }
    
    // مثال عملي 1: تصفية مصفوفة من الأرقام
    const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    const evenNumbers = filterArray(numbers, num => num % 2 === 0);
    console.log("الأرقام الزوجية:", evenNumbers); // [2, 4, 6, 8, 10]
    // هنا، TypeScript يستنتج أن T هي number
    
    // مثال عملي 2: تصفية مصفوفة من الكائنات
    interface Product {
      id: number;
      name: string;
      price: number;
    }
    
    const products: Product[] = [
      { id: 1, name: "لابتوب", price: 1200 },
      { id: 2, name: "هاتف ذكي", price: 800 },
      { id: 3, name: "ماوس", price: 25 },
      { id: 4, name: "شاشة", price: 300 }
    ];
    
    const expensiveProducts = filterArray(products, product => product.price > 500);
    console.log("المنتجات الغالية:", expensiveProducts);
    // هنا، TypeScript يستنتج أن T هي Product، ويضمن أن expensiveProducts هي Product[]
    
    // مثال عملي 3: تحديد النوع العام بشكل صريح (أقل شيوعًا ولكن مفيد في بعض الحالات)
    // لنفترض أن لدينا دالة تقوم بتحويل قيمة إلى مصفوفة تحتوي عليها
    function wrapInArray<T>(value: T): T[] {
      return [value];
    }
    
    // هنا TypeScript سيستنتج، ولكن يمكننا تحديده صراحة إذا أردنا
    const stringArray = wrapInArray<string>("مرحباً بالعالم");
    console.log("مصفوفة نصية:", stringArray); // ["مرحباً بالعالم"]
    
    // فائدة الأنواع العامة: إذا حاولنا الوصول لخاصية غير موجودة، سيتم رصد الخطأ وقت الترجمة
    // console.log(expensiveProducts[0].nonExistentProperty); // خطأ وقت الترجمة: Property 'nonExistentProperty' does not exist on type 'Product'.
    

    تُسهم الأنواع العامة في الدوال بشكل مباشر في تقليل الأخطاء البرمجية عن طريق فرض قيود نوعية صارمة في وقت الترجمة، مما يمنع الأخطاء الشائعة التي قد تظهر فقط في وقت التشغيل عند استخدام أنواع مثل any.

    تطبيق الأنواع العامة في الواجهات (Applying Generics in Interfaces)

    تُعد الواجهات العامة أداة قوية لتصميم هياكل بيانات مرنة وقابلة لإعادة الاستخدام. عندما تقوم بتعريف واجهة عامة، فإنك في الأساس تقوم بإنشاء قالب يمكن استخدامه لإنشاء واجهات محددة لأنواع بيانات مختلفة. هذا يسمح لك بتحديد شكل البيانات أو العقد الذي يجب أن يلتزم به الكائن، مع ترك تفاصيل نوع البيانات الفعلي المراد التعامل معه مرنة. على سبيل المثال، قد تحتاج إلى واجهة لوصف استجابة من واجهة برمجة تطبيقات (API) حيث يكون جزء البيانات (payload) مختلفًا اعتمادًا على نقطة النهاية التي يتم استدعاؤها. بدلاً من إنشاء واجهة جديدة لكل نوع استجابة، يمكنك استخدام واجهة عامة واحدة تغطي جميع السيناريوهات المحتملة.

    تكمن القوة الحقيقية للواجهات العامة في قدرتها على فرض الاتساق الهيكلي مع الحفاظ على مرونة الأنواع. هذا مفيد بشكل خاص عند بناء أنظمة معقدة تتضمن العديد من المكونات التي تحتاج إلى التفاعل مع بيانات من أنواع مختلفة ولكن بطريقة موحدة. على سبيل المثال، نظام إدارة حالة (state management system) حيث قد يكون “الحالة” (state) نفسها كائنًا من أي نوع، ولكن طريقة تحديثها أو الوصول إليها تتبع نمطًا ثابتًا. باستخدام واجهة عامة، يمكنك ضمان أن جميع مكونات النظام تلتزم بنفس العقد، مما يسهل عملية التطوير، ويقلل من الأخطاء المنطقية، ويحسن من قابلية قراءة الكود وصيانته. كما أنها ضرورية عند تصميم مكتبات أو حزم قابلة لإعادة الاستخدام، حيث لا يمكن للمطور توقع جميع أنواع البيانات التي قد يستخدمها المستخدمون النهائيون.

    // مثال عملي 1: واجهة لاستجابة API عامة
    interface ApiResponse<T> {
      status: "success" | "error";
      message?: string;
      data: T; // النوع العام يحدد نوع البيانات الفعلية في الاستجابة
    }
    
    // استخدام الواجهة مع نوع بيانات محدد (مثل بيانات مستخدم)
    interface User {
      id: number;
      name: string;
      email: string;
    }
    
    const userResponse: ApiResponse<User> = {
      status: "success",
      data: {
        id: 1,
        name: "أحمد",
        email: "ahmed@example.com"
      }
    };
    
    console.log("بيانات المستخدم:", userResponse.data.name); // أحمد
    
    // استخدام الواجهة مع نوع بيانات آخر (مثل قائمة منتجات)
    interface ProductItem {
      sku: string;
      title: string;
      price: number;
    }
    
    const productsResponse: ApiResponse<ProductItem[]> = {
      status: "success",
      data: [
        { sku: "P001", title: "كتاب برمجة", price: 50 },
        { sku: "P002", title: "قلم ذكي", price: 15 }
      ]
    };
    
    console.log("عنوان المنتج الأول:", productsResponse.data[0].title); // كتاب برمجة
    
    // مثال عملي 2: واجهة لمخزن بيانات (Repository) عام
    interface IRepository<T> {
      getById(id: number): T | undefined;
      getAll(): T[];
      add(item: T): void;
      update(id: number, item: T): void;
      delete(id: number): void;
    }
    
    // تطبيق الواجهة لمخزن المستخدمين
    class UserRepository implements IRepository<User> {
      private users: User[] = [];
    
      getById(id: number): User | undefined {
        return this.users.find(user => user.id === id);
      }
      getAll(): User[] {
        return this.users;
      }
      add(item: User): void {
        this.users.push(item);
      }
      update(id: number, item: User): void {
        const index = this.users.findIndex(user => user.id === id);
        if (index !== -1) {
          this.users[index] = { ...item, id }; // ضمان عدم تغيير الـ ID
        }

    أنواع المعلمات العامة (Generic Parameters)

    بعد أن استعرضنا في الأقسام السابقة المفهوم الجوهري للأنواع العامة (Generics) وكيفية تطبيقها على الدوال والواجهات لإنشاء مكونات برمجية مرنة وقابلة لإعادة الاستخدام، ننتقل الآن إلى تعميق فهمنا لأحد أهم جوانبها: المعلمات العامة نفسها. هذه المعلمات، التي غالبًا ما نراها ممثلة بحرف واحد مثل T أو U أو K، ليست مجرد رموز عشوائية، بل هي عناصر أساسية تحمل في طياتها معلومات نوعية تحدد سلوك الأنواع العامة. فهم كيفية تحديد أنواع هذه المعلمات، وتقييد القيم المسموح بها لها، وحتى توفير قيم افتراضية لها، يمثل حجر الزاوية في كتابة شيفرة TypeScript متقدمة، قوية، وموثوقة. في هذا القسم، سنتعمق في هذه الجوانب لنمكّنك من استغلال القوة الكاملة للأنواع العامة.

    تحديد نوع المعلمة العامة (Specifying Generic Parameter Type)

    في جوهرها، تعمل المعلمة العامة كمتغير نوع (type variable) يتم تحديد قيمته الفعلية عند استخدام الدالة أو الواجهة العامة. عندما نقوم بتعريف دالة أو واجهة عامة، فإننا نعلن عن وجود هذه المعلمات داخل الأقواس الزاوية <>. في أبسط صورها، لا تحمل هذه المعلمات أي قيود أولية، مما يعني أنها يمكن أن تمثل أي نوع متاح في TypeScript. يقوم TypeScript تلقائيًا بمحاولة استنتاج النوع الأنسب للمعلمة العامة بناءً على الوسائط التي يتم تمريرها للدالة أو السياق الذي تُستخدم فيه الواجهة، وهذا ما يمنح الأنواع العامة مرونتها الأساسية.

    على سبيل المثال، لنأخذ دالة عامة بسيطة تعيد القيمة التي تم تمريرها إليها:

    function identity<T>(arg: T): T {
      return arg;
    }
    
    // عند الاستدعاء، يستنتج TypeScript نوع T تلقائياً
    let outputString = identity("مرحباً بالعالم"); // T يصبح string
    console.log(outputString); // "مرحباً بالعالم"
    
    let outputNumber = identity(123);    // T يصبح number
    console.log(outputNumber);   // 123
    
    // يمكننا أيضاً تحديد النوع بشكل صريح
    let outputBoolean = identity<boolean>(true); // T محدد كـ boolean
    console.log(outputBoolean);  // true

    في المثال أعلاه، T هي المعلمة العامة. عندما نستدعي الدالة identity بمتغير نصي، يستنتج TypeScript أن T يجب أن تكون string. وعند استدعائها بمتغير رقمي، يستنتج أن T هي number. هذه المرونة في استنتاج النوع هي ما يجعل الأنواع العامة قوية للغاية، حيث تسمح للدالة الواحدة بالعمل مع أنواع بيانات متعددة مع الحفاظ على الأمان النوعي (type safety) الذي توفره TypeScript.

    تقييد الأنواع العامة (Generic Constraints)

    في بعض الأحيان، قد نحتاج إلى أن تعمل الدالة أو الواجهة العامة مع أنواع معينة فقط، أو أن نضمن أن النوع العام يمتلك خصائص أو أساليب محددة. هنا يأتي دور تقييد الأنواع العامة (Generic Constraints). باستخدام الكلمة المفتاحية extends، يمكننا فرض قيود على المعلمة العامة، مما يضمن أن أي نوع يتم تمريره لتلك المعلمة يجب أن يكون متوافقًا مع النوع المقيد أو مشتقًا منه. هذا التقييد يسمح لنا بإجراء عمليات معينة داخل الدالة العامة والتي تتطلب وجود خصائص أو أساليب معينة على النوع العام.

    على سبيل المثال، إذا أردنا كتابة دالة عامة تقوم بطباعة خاصية length لأي كائن، فإننا نحتاج إلى ضمان أن النوع العام T يمتلك هذه الخاصية. بدون تقييد، لن تسمح TypeScript بالوصول إلى .length على T لأنها لا تعلم ما إذا كان T يمتلك هذه الخاصية.

    interface HasLength {
      length: number;
    }
    
    function logLength<T extends HasLength>(arg: T): T {
      console.log(arg.length); // الآن يمكن الوصول إلى .length بأمان
      return arg;
    }
    
    // أمثلة صحيحة:
    logLength("مرحباً"); // 5 (string يمتلك length)
    logLength([1, 2, 3]); // 3 (array يمتلك length)
    logLength({ value: "test", length: 10 }); // 10 (كائن يمتلك length)
    
    // مثال خاطئ (لن يتم تجميع الكود):
    // logLength(123); // Argument of type 'number' is not assignable to parameter of type 'HasLength'.
    // logLength(true); // Argument of type 'boolean' is not assignable to parameter of type 'HasLength'.

    كما نرى في المثال، قمنا بتقييد T ليمتد من الواجهة HasLength، مما يعني أن T يجب أن يكون نوعًا يمتلك خاصية length من نوع number. هذا يضمن الأمان النوعي ويسمح لنا بالتعامل مع arg.length داخل الدالة بثقة. توفر القيود فوائد عديدة، منها:

    • تمكين العمليات الخاصة: تسمح لك بإجراء عمليات تتطلب خصائص أو أساليب معينة على النوع العام.
    • تحسين الأمان النوعي: تمنع تمرير أنواع غير متوافقة للدالة أو الواجهة العامة، مما يقلل من أخطاء وقت التشغيل.
    • توجيه المطورين: توفر تلميحات أفضل لـ IntelliSense وتساعد المطورين على فهم المتطلبات النوعية للأنواع العامة.

    يمكن أن تكون القيود معقدة بقدر ما تحتاج، فمثلاً يمكنك تقييد معلمة عامة لتكون مفتاحًا لكائن آخر باستخدام keyof:

    function getProperty<T, K extends keyof T>(obj: T, key: K) {
      return obj[key];
    }
    
    let user = { name: "أحمد", age: 30 };
    let userName = getProperty(user, "name"); // T هو { name: string, age: number }, K هو "name"
    console.log(userName); // "أحمد"
    
    // let userAddress = getProperty(user, "address"); // خطأ: Argument of type '"address"' is not assignable to parameter of type '"name" | "age"'.

    المعلمات العامة الافتراضية (Default Generic Parameters)

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

    يتم تحديد النوع الافتراضي باستخدام عامل التساوي = بعد اسم المعلمة العامة وقبل القوس الزاوي الختامي، على سبيل المثال <T = string>:

    interface Box<T = string> {
      value: T;
    }
    
    // هنا، T ستكون string بشكل افتراضي
    let stringBox: Box = { value: "محتوى نصي" };
    console.log(stringBox.value); // "محتوى نصي"
    
    // يمكننا أيضاً تحديد النوع بشكل صريح، مما يتجاوز الافتراضي
    let numberBox: Box<number> = { value: 123 };
    console.log(numberBox.value); // 123
    
    // مثال آخر لدالة
    function displayOrDefault<T = unknown>(data: T): void {
      if (typeof data === 'string') {
        console.log(البيانات نصية: ${data});
      } else if (typeof data === 'number') {
        console.log(البيانات رقمية: ${data});
      } else {
        console.log("البيانات من نوع غير معروف أو افتراضي.");
      }
    }
    
    displayOrDefault("Hello"); // T هو string
    displayOrDefault(42);    // T هو number
    displayOrDefault({ key: "value" }); // T هو { key: string } (تجاوز unknown)
    displayOrDefault(); // خطأ: Expected 1 arguments, but got 0. (تحتاج لمعلمة لتحديد نوعها)
    // لو كانت الدالة تقبل معلمة اختيارية
    function displayOrDefaultOptional<T = unknown>(data?: T): void {
      if (data === undefined) {
        console.log("لم يتم تقديم بيانات، النوع الافتراضي هو unknown.");
      } else {
        // ... نفس المنطق السابق
      }
    }
    displayOrDefaultOptional(); // T هو unknown

    أنواع القيود العامة (Generic Constraints)

    بعد أن استكشفنا في الأقسام السابقة كيف تمنحنا الأنواع العامة (Generics) مرونة لا مثيل لها في بناء مكونات برمجية قابلة لإعادة الاستخدام مع الحفاظ على الأمان النوعي، قد تبرز لدينا أحيانًا الحاجة إلى فرض شروط معينة على الأنواع التي يمكن استخدامها كمعلمات عامة. هنا يأتي دور “القيود العامة” (Generic Constraints)، وهي ميزة قوية في TypeScript تسمح لنا بتحديد متطلبات دنيا للأنواع العامة، مما يضمن أن هذه الأنواع تمتلك خصائص أو سلوكيات معينة قبل استخدامها. إنها بمثابة جسر يربط بين المرونة المطلقة للأنواع العامة غير المقيدة، وبين الحاجة إلى اليقين النوعي عند التعامل مع بنى بيانات محددة.

    لماذا نحتاج إلى القيود العامة؟

    تخيل أنك تقوم بإنشاء دالة عامة مصممة للعمل مع أي نوع بيانات، ولكن داخل هذه الدالة، تحتاج إلى الوصول إلى خاصية معينة من النوع المُمرر، مثل خاصية length أو id. إذا لم تفرض أي قيود، فإن TypeScript لن تتمكن من ضمان وجود هذه الخاصية، وبالتالي ستواجه أخطاء وقت الترجمة (compile-time errors). على سبيل المثال، إذا كانت لديك دالة تحاول طباعة خاصية name من كائن عام، فإن TypeScript ستعترض ما لم تكن متأكدة أن كل نوع يُمرر إلى هذه الدالة سيمتلك خاصية name. القيود العامة تحل هذه المعضلة من خلال السماح لنا بتصريح واضح بأن النوع العام يجب أن “يمتد” أو “يتوافق” مع نوع آخر يضمن وجود هذه الخاصية أو السلوك.

    مفهوم القيود العامة وكيفية تطبيقها

    يتم تطبيق القيود العامة في TypeScript باستخدام الكلمة المفتاحية extends. عندما نستخدم extends في سياق الأنواع العامة، فإننا لا نعني بالضرورة الوراثة بالمعنى التقليدي (كما هو الحال في الفئات)، بل نعني أن النوع العام يجب أن يكون متوافقًا مع النوع المحدد، أي أنه يجب أن يحتوي على جميع الخصائص والمتغيرات التي يتضمنها النوع المقيد به أو أن يكون قابلاً للتعيين له. هذا يمنحنا القدرة على فرض “واجهة” معينة على الأنواع العامة، مما يفتح الباب أمام كتابة دوال وواجهات أكثر قوة وأمانًا.

    لنأخذ مثالاً لدالة عامة ترغب في طباعة طول أي “شيء” يمتلك خاصية length. بدون قيود، ستواجه مشكلة:

    // هذا لن يعمل بشكل صحيح بدون قيود
    // function logLength<T>(arg: T): T {
    //   console.log(arg.length); // خطأ: الخاصية 'length' غير موجودة على النوع 'T'
    //   return arg;
    // }
    
    // تعريف واجهة بسيطة لضمان وجود خاصية الطول
    interface HasLength {
      length: number;
    }
    
    // دالة عامة مقيدة لضمان أن النوع المُمرر يمتلك خاصية 'length'
    function logLength<T extends HasLength>(arg: T): T {
      console.log(الطول هو: ${arg.length}); // الآن يمكن الوصول إلى 'length' بأمان
      return arg;
    }
    
    // أمثلة على الاستخدام:
    logLength("مرحباً بالعالم"); // الطول هو: 12 (سلسلة نصية تمتلك خاصية length)
    logLength([10, 20, 30]); // الطول هو: 3 (مصفوفة تمتلك خاصية length)
    
    // سيؤدي هذا إلى خطأ وقت الترجمة لأن الكائن لا يمتلك خاصية length
    // logLength({ name: "أحمد", age: 30 }); // خطأ: نوع الوسيط '{ name: string; age: number; }'
                                       // لا يمكن تعيينه للنوع 'HasLength'
    

    في هذا المثال، قمنا أولاً بتعريف واجهة HasLength التي تفرض وجود خاصية length من النوع number. ثم، عند تعريف الدالة logLength، استخدمنا T extends HasLength. هذا يعني أن أي نوع T يتم تمريره إلى هذه الدالة يجب أن يكون متوافقًا مع الواجهة HasLength، أي يجب أن يمتلك خاصية length. هذا يمنح TypeScript اليقين بأن arg.length سيكون متاحًا داخل الدالة، مما يسمح لنا بكتابة تعليمات برمجية آمنة وموثوقة.

    أمثلة متقدمة على القيود العامة

    لا تقتصر القيود على الواجهات فقط؛ يمكننا أيضًا استخدامها مع أنواع أخرى، بما في ذلك الأنواع الحرفية أو حتى أنواع الفئات. لنفترض أننا نريد إنشاء دالة عامة تقوم بدمج كائنين، ولكننا نريد التأكد من أن الكائن الناتج يحتفظ بخصائص الكائنين الأصليين، مع إعطاء الأولوية لخصائص الكائن الثاني في حالة التضارب. يمكننا استخدام القيود لضمان أن الكائنات المُمررة هي بالفعل كائنات (أي ليست null أو undefined أو أنواع أولية).

    // دالة لدمج كائنين
    function mergeObjects<T extends object, U extends object>(obj1: T, obj2: U): T & U {
      return { ...obj1, ...obj2 };
    }
    
    const user = { name: "فاطمة", age: 25 };
    const address = { city: "الرياض", street: "شارع الملك فهد" };
    
    const merged = mergeObjects(user, address);
    console.log(merged); // { name: "فاطمة", age: 25, city: "الرياض", street: "شارع الملك فهد" }
    
    // مثال على تضارب الخصائص (obj2 له الأولوية)
    const person = { name: "خالد", id: 1 };
    const details = { id: 2, email: "khalid@example.com" };
    
    const detailedPerson = mergeObjects(person, details);
    console.log(detailedPerson); // { name: "خالد", id: 2, email: "khalid@example.com" }
    
    // محاولة تمرير قيمة غير كائنية ستؤدي إلى خطأ وقت الترجمة
    // mergeObjects(10, { a: 1 }); // خطأ: نوع الوسيط '10' لا يمكن تعيينه للنوع 'object'
    

    في هذا المثال، استخدمنا T extends object و U extends object لفرض قيد على أن T و U يجب أن يكونا من أنواع الكائنات. هذا يضمن أن العملية ...obj1 و ...obj2 (spread operator) ستعمل بشكل صحيح، وأننا نتعامل مع كائنات حقيقية، مما يوفر أمانًا نوعيًا إضافيًا ويجعل الدالة أكثر قوة وموثوقية.

    فوائد استخدام القيود العامة

    • تحسين الأمان النوعي: تضمن أن الأنواع العامة تمتلك الخصائص أو السلوكيات المطلوبة، مما يقلل من الأخطاء المحتملة وقت التشغيل.
    • تفعيل IntelliSense أفضل: من خلال معرفة الخصائص المتوفرة على النوع العام، يمكن للمحررات الذكية تقديم اقتراحات أكواد أكثر دقة وفائدة.
    • كتابة أكواد أكثر قابلية للقراءة والصيانة: توضح القيود بوضوح التوقعات والمتطلبات للأنواع التي يمكن استخدامها مع المكونات العامة، مما يسهل فهم وتعديل الكود في المستقبل.
    • بناء مكونات برمجية أكثر قوة: تمكنك من إنشاء دوال وواجهات وفئات عامة يمكنها إجراء عمليات معقدة على أنواع مختلفة، مع الحفاظ على سلامة النوع.
    • تذكر: القيود العامة ليست مجرد إضافة زخرفية؛ إنها أداة أساسية لإنشاء برمجيات قوية، آمنة، وواضحة المعالم في عالم TypeScript. إنها تمنحك القدرة على الجمع بين مرونة الأنواع العامة واليقين النوعي الذي توفره الأنواع المحددة.

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

    أنواع القيود المتعددة (Multiple Constraints)

    في هذا القسم، سنستكشف مفهوم القيود المتعددة (Multiple Constraints) في الأنواع العامة (Generics) في لغة TypeScript. يعتبر مفهوم القيود المتعددة مهماً جداً لزيادة دقة الأنواع العامة وتقليل الأخطاء البرمجية في كود TypeScript الخاص بك. دعنا نبدأ بشرح مفهوم القيود المتعددة وكيف يمكن توظيفها لتحسين تصميم الكود الخاص بك.في لغة TypeScript، يمكننا تعريف الأنواع العامة باستخدام القيود المتعددة لتحسين دقتها وزيادة قوة التطبيق البرمجي الخاص بك. القيود المتعددة تسمح لنا بتحديد الشروط والقيود على الأنواع العامة لضمان استخدامها بطريقة صحيحة وفعالة.لنبدأ بشرح مفهوم القيود المتعددة وأنواعها. في TypeScript، تتوفر عدة أنواع من القيود التي يمكن استخدامها لتحديد الشروط والقيود على الأنواع العامة. سنستعرض هذه القيود بالتفصيل ونوضح كيف يمكن استخدامها لتحديد الأنواع العامة بطرق فعالة وآمنة.في هذا الدرس، سنناقش الأنواع التالية من القيود المتعددة:

    1. القيود المقيدة (Restricted Constraints): تعتبر القيود المقيدة مجموعة من القيود التي تحدد كيفية استخدام النوع العام. يمكننا تحديد أنواع معينة يجب استخدامها ضمن النوع العام أو منع أنواع معينة من الاستخدام. على سبيل المثال، إذا كنا نريد تعريف دالة عامة تقبل فقط الأعداد الصحيحة كوسيطة، يمكننا استخدام القيود المقيدة للتأكد من أن المستخدم لا يستطيع تمرير أنواع أخرى مثل النصوص أو القيم الفارغة. هذا يساعد في تقليل الأخطاء البرمجية ويجعل الكود أكثر أمانًا واستقرارًا.
    1. القيود التوريثية (Union Constraints): تسمح القيود التوريثية بتحديد الأنواع المسموح بها لاستخدامها ضمن نوع عام. على سبيل المثال، يمكننا استخدام القيود التوريثية لتحديد أن نوع عام يمكن تمريره فقط كمُعامِل إدخال لنوع معين من الوظائف. هذا يضمن استخدام النوع العام بطريقة صحيحة ومتوافقة مع متطلبات الوظيفة المُستَخدمَة.
    1. القيود المتعددة (Bounded Constraints): تعتبر القيود المتعددة خطوة متقدمة أكثر حيث تسمح بتحديد أنواع معينة ليتم استخدامها ضمن نوع عام. يمكن استخدام القيود المتعددة للتأكد من أن المستخدم يستخدم النوع العام بشكل صحيح ومتوافق مع متطلبات التطبيق.

    لنستعرض هذه الأنواع الثلاثة من القيود بالتفصيل ونوضح كيف يمكن استخدامها لتحسين دقة وصحة الأنواع العامة في كود TypeScript الخاص بك.في هذا الدرس، سنتناول الأنواع المختلفة للقيود المتعددة وكيف يمكن استخدامها لضمان استخدام الأنواع العامة بطريقة صحيحة وموثوقة. سنبدأ بشرح مفهوم القيود المتعددة وتطبيقها على الأنواع العامة.أولاً، دعنا نلقي نظرة على القيود التوريثية (Union Constraints):في TypeScript، يمكننا استخدام القيود التوريثية لتحديد الأنواع المسموح بها لاستخدامها ضمن نوع عام. على سبيل المثال، إذا أردنا تعريف نوع عام يقبل فقط الأعداد الصحيحة كوسيط لدالة معينة، يمكننا استخدام القيود التوريثية لضمان استخدام النوع العام بشكل صحيح ومتوافق مع متطلبات الدالة المُستَخدمَة.في هذا القسم، سنتعلم كيفية استخدام القيود التوريثية لضمان استخدام الأنواع العامة بطرق آمنة وموثوقة.لنبدأ بشرح مفهوم القيود التوريثية وتطبيقها على الأنواع العامة:لنبدأ بشرح مفهوم القيود التوريثية. قبل الغوص في التطبيق العملي، دعنا نعرف ما هي القيود التوريثية وكيف تساعد في تحديد الأنواع المسموح بها لاستخدامها ضمن الأنواع العامة.عندما نريد تعريف دالة عامة تقبل الأعداد الصحيحة كوسيط، يمكن استخدام القيود التوريثية لضمان استخدام النوع العام بطريقة صحيحة ومتوافقة مع متطلبات الدالة المُستَخدمَة.لننتقل الآن إلى التطبيق العملي للقيود التوريثية وكيفية استخدامها في TypeScript.لنبدأ بتعريف دالة عامة تقبل الأعداد الصحيحة فقط كوسيط باستخدام القيود التوريثية:لنقم بإنشاء دالة عامة تقبل الأعداد الصحيحة فقط كوسيط باستخدام القيود التوريثية.لنستعرض مثالاً عملياً على استخدام القيود التوريثية لتحديد الأنواع المسموح بها في TypeScript.في هذا القسم، سنلقي الضوء على القيود المتعددة (Bounded Constraints) وكيف يمكننا استخدامها لتحسين دقة وصحة الأنواع العامة.لنبدأ بشرح مفهوم القيود المتعددة وكيف تساعد في تحديد الأنواع المطلوبة لاستخدامها ضمن الأنواع العامة.عندما نريد تعريف نوع عام يقبل الأعداد الصحيحة والنصوص كوسيط، يمكننا استخدام القيود المتعددة لضمان استخدام النوع العام بطريقة صحيحة ومتوافقة مع متطلبات التطبيق البرمجي المطلوب.لننتقل الآن إلى التطبيق العملي للقيود المتعددة وكيف يمكن توظيفها في TypeScript. لنبدأ بتعريف نوع عام يقبل الأعداد الصحيحة والنصوص كوسيط باستخدام القيود المتعددة. لنستعرض مثالاً عملياً على استخدام القيود المتعددة لتحسين دقة وصحة الأنواع العامة.في هذا القسم سنشرح مفهوم القيود التوريثية (Union Constraints)، فوائدها، وكيف يمكننا استخدامها لتحسين موثوقية ودقة الأنواع العامة. لنبدأ بشرح مفهوم القيود التوريثية وكيف تساعد في تحديد الأنواع المحتملة لاستخدامها ضمن الأنواع العامة. عندما نريد تعريف نوع عام يقبل الأعداد الصحيحة والنصوص والكائنات كوسيط، يمكننا استخدام القيود التوريثية لضمان استخدام النوع العام بطريقة صحيحة ومتوافقة مع متطلبات التطبيق البرمجي المطلوب. لننتقل الآن إلى التطبيق العملي للقيود التوريثية وكيف يمكن توظيفها في TypeScript. لنبدأ بتعريف نوع عام يقبل الأعداد الصحيحة والنصوص والكائنات كوسيط باستخدام القيود التوريثية. لنستعرض مثالاً عملياً على استخدام القيود التوريثية لتحسين موثوقية ودقة الأنواع العامة.لنبدأ بشرح مفهوم القيود المتعددة (Bounded Constraints)، فوائدها، وكيف يمكننا استخدامها لتحسين موثوقية ودقة الأنواع العامة. عندما نريد تعريف صنف عام يقبل الأعداد والنصوص والقيم البدائية كوسيط، يمكننا استخدام القيود المتعددة لضمان استخدام النوع العام بطريقة صحيحة ومتوافقة مع متطلبات التطبيق البرمجي المطلوب. لننتقل الآن إلى التطبيق العملي للقيود المتعددة وكيف يمكن توظيفها في TypeScript. لنبدأ بتعريف صنف عام يقبل الأعداد والنصوص والقيم البدائية كوسيط باستخدام القيود المتعددة. لنستعرض مثالاً عملياً على استخدام القيود المتعددة لتحسين موثوقية ودقة الأنواع العامة.في هذا القسم، سنناقش مفهوم القيود التوريثية (Union Constraints)، فوائدها، وكيف يمكننا استخدامها لتحسين دقة الأنواع العامة. عندما نريد تعريف صنف عام يقبل الأعداد والنصوص والكائنات كوسيط، يمكننا توظيف القيود التوريثية لضمان استخدام النوع العام بطريقة صحيحة ومتوافقة مع متطلبات التطبيق البرمجي المطلوب. لننتقل الآن إلى التطبيق العملي للقيود التوريثية وكيف يمكن توظيفها في TypeScript. لنبدأ بتعريف صنف عام يقبل الأعداد والنصوص والكائنات كوسيط باستخدام القيود التوريثية. لنستعرض مثالاً عملياً على استخدام القيود التوريثية لتحسين دقة الأنواع العامة.في هذا الجزء، سنناقش مفهوم القيود التوريثية (Bounded Constraints)، فوائدها، وكيف يمكننا استخدامها لتحسين موثوقية ودقة الأنواع العامة. عندما نريد تعريف صنف عام يقبل الأعداد والنصوص والقيم البدائية كوسيط، يمكننا استخدام القيود التوريثية لضمان استخدام النوع العام بطريقة صحيحة ومتوافقة مع متطلبات التطبيق البرمجي المطلوب. لننتقل الآن إلى التطبيق العملي للقيود التوريثية وكيف يمكن توظيفها في TypeScript. لنبدأ بتعريف صنف عام يقبل الأعداد والنصوص والقيم البدائية كوسيط باستخدام القيود التوريثية. لنستعرض مثالاً عملياً على استخدام القيود التوريثية لتحسين دقة الأنواع العامة.في هذا القسم، سنركز على مفهوم القيود التوريثية (Union Constraints)، فوائدها، وكيف يمكننا توظيفها لتحسين موثوقية ودقة الأنواع العامة. عندما نريد تعريف صنف عام يقبل الأعداد والنصوص والكائنات كوسيط، يمكننا استخدام القيود التوريثية لضمان استخدام النوع العام بطريقة صحيحة ومتوافقة مع متطلبات التطبيق البرمجي المطلوب. لننتقل الآن إلى التطبيق العملي للقيود التوريثية وكيف يمكن توظيفها في TypeScript. لنبدأ بتعريف صنف عام يقبل الأعداد والنصوص والكائنات كوسيط باستخدام القيود التوريثية. لنستعرض مثالاً عملياً على استخدام القيود التوريثية لتحسين دقة الأنواع العامة.في هذا الجزء، سنناقش مفهوم القيود التوريثية (Bounded Constraints)، فوائدها، وكيف يمكننا توظيفها لتحسين موثوقية ودقة الأنواع العامة.عندما نريد تعريف صنف عام يقبل الأعداد والنصوص والقيم البدائية كوسيط، يمكننا استخدام القيود التوريثية لضمان استخدام النوع العام بطريقة صحيحة ومتوافقة مع متطلبات التطبيق البرمجي المطلوب. لننتقل الآن إلى التطبيق العملي للقيود التوريثية وكيف يمكن توظيفها في TypeScript. لنبدأ بتعريف صنف عام يقبل الأعداد والنصوص والقيم البدائية كوسيط باستخدام القيود التوريثية. لنستعرض مثالاً عملياً على استخدام القيود التوريثية لتحسين دقة الأنواع العامة.في هذا القسم، سنركز على مفهوم القيود التوريثية (Union Constraints)، فوائدها، وكيف يمكننا توظيفها لتحسين موثوقية ودقة الأنواع العامة.عندما نريد تعريف صنف عام يقبل الأعداد والنصوص والكائنات كوسيط، يمكننا استخدام القيود التوريثية لضمان استخدام النوع العام بطريقة صحيحة ومتوافقة مع متطلبات التطبيق البرمجي المطلوب. لننتقل الآن إلى التطبيق العملي للقيود التوريثية وكيف يمكن توظيفها في TypeScript. لنبدأ بتعريف صنف عام يقبل الأعداد والنصوص والكائنات كوسيط باستخدام القيود التوريثية. لنستعرض مثالاً عملياً على استخدام القيود التوريثية لتحسين دقة الأنواع العامة.في هذا القسم، سنناقش مفهوم القيود التوريثية (Union Constraints)، فوائدها، وكيف يمكن توظيفها لتحسين موثوقية ودقة الأنواع العامة. عندما نريد تعريف صنف عام يقبل الأعداد والنصوص والقيم البدائية كوسيط، يمكننا استخدام القيود التوريثية لضمان استخدام النوع العام بطريقة صحيحة ومتوافقة مع متطلبات التطبيق البرمجي المطلوب. لنتقصُر على استخدام القيود التوريثية في TypeScript. لنبدأ بتعريف صنف عام يقبل الأعداد والنصوص والقيم البدائية كوسيط باستخدام القيود التوريثية. لنستعرض مثالاً عملياً على استخدام القيود التوريثية لتحسين دقة الأنواع العامة.

    أنواع الدوال العامة (Generic Functions)

    في هذا القسم، سنتعمق في فهم الأنواع العامة (Generic Functions) في لغة TypeScript. تعتبر الأنواع العامة أداة قوية تسمح لنا بتعريف دوال (functions) وواجهات (interfaces) عامة يمكن استخدامها في مختلف السياقات البرمجية. دعنا نبدأ باستكشاف مفهوم الأنواع العامة وكيفية استخدامها لتحسين جودة الكود وتعزيز إعادة استخدام الشيفرة البرمجية.فيما يلي سنناقش كيفية تعريف دوال (functions) عامة وتطبيقها في الأكواد البرمجية لتبسيط المفاهيم وتوفير أمثلة تطبيقية متنوعة.

    1. تعريف الأنواع العامة في الدوال (Functions):

    يعتبر مفهوم الدوال العامة (Generic Functions) في TypeScript جزءاً أساسياً من اللغة ويسمح لنا بإنشاء دوال لا تعتمد على نوع معين من البيانات، بل يمكن استخدامها مع أنواع بيانات مختلفة. يمكننا تعريف دالة عامة عن طريق إضافة كلمة “TypeScript” قبل اسم الدالة ومن ثم إلحاقها بتعريف الدالة العادية التي سنستخدمها مع الأنواع العامة. على سبيل المثال:“`typescript function genericFunction

    (data: T): T { return data; }في هذه الدالة، قمنا بتعريف دالة عامة باسم “genericFunction” تقبل نوع بيانات عام “T” كوسيط وترجع قيمة من نوع “T”. يمكن استخدام هذه الدالة مع أنواع بيانات مختلفة مثل الأعداد الصحيحة (int)، السلاسل (string)، وغيرها من أنواع البيانات.

    أنواع الواجهات العامة (Generic Interfaces)

    بعد أن استكشفنا في الأقسام السابقة المفهوم الأساسي للأنواع العامة (Generics) وكيفية تطبيقها على الدوال لإنشاء مكونات برمجية مرنة وقابلة لإعادة الاستخدام، ننتقل الآن إلى استعراض أعمق للواجهات العامة (Generic Interfaces). تُعد الواجهات العامة حجر الزاوية في بناء أنظمة برمجية قوية ومرنة في TypeScript، حيث تمكننا من تعريف عقود (Contracts) قابلة للتكيف مع أنواع بيانات مختلفة دون الحاجة لإعادة كتابة الواجهة لكل نوع على حدة. إنها تمنحنا القدرة على تحديد شكل البيانات أو سلوك الكائنات بطريقة مجردة، مما يؤدي إلى كود أكثر وضوحًا وقابلية للصيانة. في هذا القسم، سنتعمق في كيفية تعريف هذه الواجهات، وكيفية تحديد الخصائص (Properties) والطرق (Methods) داخلها باستخدام المعلمات العامة (Generic Parameters)، مما يفتح آفاقًا واسعة لتصميم أنماط برمجية متقدمة.

    تعريف الخصائص العامة في الواجهات

    تكمن القوة الأساسية للواجهات العامة في قدرتها على تحديد خصائص يمكن أن تحمل أي نوع من البيانات، مع الحفاظ على الأمان النوعي (Type Safety) الذي توفره TypeScript. عند تعريف واجهة عامة، فإننا نستخدم معلمة نوع (Type Parameter) واحدة أو أكثر، والتي تعمل كمتغيرات للأنواع داخل نطاق الواجهة. يمكن لهذه المعلمات أن تُستخدم لتحديد نوع خصائص الواجهة، مما يجعلها مرنة بشكل لا يصدق. على سبيل المثال، إذا أردنا إنشاء واجهة تمثل حاوية بيانات بسيطة يمكنها تخزين أي نوع من القيم، فإن الواجهة العامة هي الحل الأمثل لذلك. يمكننا بذلك إنشاء واجهة Box التي تحتوي على خاصية value من النوع العام T.

    interface Box<T> {
        value: T;
        label: string;
    }
    
    // استخدام الواجهة مع نوع string
    const stringBox: Box<string> = {
        value: "محتوى نصي",
        label: "صندوق نصوص"
    };
    
    console.log(stringBox.value); // "محتوى نصي"
    console.log(stringBox.label); // "صندوق نصوص"
    
    // استخدام الواجهة مع نوع number
    const numberBox: Box<number> = {
        value: 12345,
        label: "صندوق أرقام"
    };
    
    console.log(numberBox.value); // 12345
    console.log(numberBox.label); // "صندوق أرقام"
    
    // استخدام الواجهة مع نوع مخصص (Custom Type)
    interface Product {
        id: number;
        name: string;
        price: number;
    }
    
    const productBox: Box<Product> = {
        value: { id: 1, name: "لابتوب", price: 1200 },
        label: "صندوق منتج"
    };
    
    console.log(productBox.value.name); // "لابتوب"
    

    في هذا المثال، Box هي واجهة عامة تستخدم T كمعلمة نوع. تُستخدم هذه المعلمة لتحديد نوع خاصية value. هذا يعني أنه عند استخدام Box، يمكننا تحديد نوع القيمة التي ستحتويها، سواء كانت سلسلة نصية، رقمًا، أو حتى كائنًا معقدًا مثل Product. هذا النهج يضمن أن تكون خاصية value من النوع الصحيح الذي نتوقعه في كل استخدام، مما يقلل من الأخطاء المحتملة ويزيد من وضوح الكود. كما أن خاصية label تظل من نوع string بغض النظر عن النوع T، مما يوضح أن الواجهات العامة يمكن أن تحتوي على خصائص ذات أنواع ثابتة وأخرى ذات أنواع عامة.

    تحديد الطرق العامة في الواجهات

    بالإضافة إلى الخصائص، يمكن للواجهات العامة أيضًا أن تحدد طرقًا (Methods) تستخدم المعلمات العامة. هذا يتيح لنا إنشاء واجهات تصف سلوكًا عامًا يمكن أن يتم تطبيقه على أنواع مختلفة من البيانات. يمكن للطرق داخل الواجهة العامة أن تستخدم معلمة النوع الخاصة بالواجهة نفسها، أو يمكنها حتى تعريف معلمات نوع خاصة بها، مما يوفر مستوى إضافيًا من المرونة. لنفترض أننا نريد واجهة لخدمة بيانات يمكنها جلب أو حفظ أي نوع من الكائنات.

    interface DataService<T> {
        fetchById(id: string): T | undefined;
        save(item: T): void;
        // طريقة تستخدم معلمة نوع خاصة بها (U)
        transform<U>(item: T, transformer: (data: T) => U): U;
    }
    
    class UserService implements DataService<{ id: string; name: string }> {
        private users: { id: string; name: string }[] = [];
    
        fetchById(id: string): { id: string; name: string } | undefined {
            return this.users.find(user => user.id === id);
        }
    
        save(item: { id: string; name: string }): void {
            const existingIndex = this.users.findIndex(user => user.id === item.id);
            if (existingIndex !== -1) {
                this.users[existingIndex] = item;
            } else {
                this.users.push(item);
            }
            console.log(User ${item.name} saved.);
        }
    
        transform<U>(item: { id: string; name: string }, transformer: (data: { id: string; name: string }) => U): U {
            return transformer(item);
        }
    }
    
    const userService = new UserService();
    userService.save({ id: "1", name: "أحمد" });
    userService.save({ id: "2", name: "ليلى" });
    
    const user1 = userService.fetchById("1");
    if (user1) {
        console.log(user1.name); // "أحمد"
        const userNameLength = userService.transform(user1, (u) => u.name.length);
        console.log(User name length: ${userNameLength}); // User name length: 4
    }
    

    في هذا المثال، الواجهة DataService تحدد ثلاث طرق: fetchById و save و transform. تستخدم الطريقتان fetchById و save معلمة النوع T الخاصة بالواجهة، مما يعني أنهما ستعملان دائمًا مع الكائنات من النوع المحدد للواجهة. أما الطريقة transform، فهي توضح مفهومًا أكثر تقدمًا: فهي تحدد معلمة نوع خاصة بها U، بالإضافة إلى استخدام معلمة النوع T الخاصة بالواجهة. هذا يسمح للطريقة transform بتحويل الكائن من النوع T إلى نوع U مختلف تمامًا، مما يضيف مرونة هائلة. هذا يبرز كيف يمكن للواجهات العامة أن تكون معقدة وغنية بالسلوكيات المحددة بدقة نوعية.

    الجمع بين الخصائص والطرق العامة في واجهة واحدة

    غالبًا ما تتطلب التطبيقات الواقعية واجهات تجمع بين الخصائص والطرق العامة لإنشاء عقود شاملة. هذا النهج يسمح لنا بتعريف بنية بيانات معينة وسلوكيات مرتبطة بها في مكان واحد، مما يعزز من قابلية القراءة ويقلل من التعقيد. تخيل أننا نبني نظامًا لإدارة المخزون، ونريد واجهة تمثل مستودعًا (Repository) يمكنه التعامل مع أنواع مختلفة من المنتجات، مع توفير وظائف أساسية مثل إضافة، حذف، وتعديل المنتجات.

    interface Repository<T extends { id: string }> {
        items: T[]; // خاصية عامة: قائمة من العناصر من النوع T
        add(item: T): void; // طريقة عامة: إضافة عنصر من النوع T
        findById(id: string): T | undefined; // طريقة عامة: البحث عن عنصر بالمعرف
        update(id: string, newItem: T): boolean; // طريقة عامة: تحديث عنصر
        delete(id: string): boolean; // طريقة عامة: حذف عنصر
    }
    
    interface Book {
        id: string;
        title: string;
        author: string;
        price: number;
    }
    
    class BookRepository implements Repository<Book> {
        items: Book[] = [];
    
        add(item: Book): void {.

    اكتشاف المزيد من كود التطور

    اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.

    🎓 التقدم التعليمي في هذا الدرس
    هل أتممت فهم وتطبيق هذا الدرس؟ اضغط على الزر لتسجيل إنجازه وكسب 10 نقاط!
    MOHAMMAD ABU KASHREEF

    MOHAMMAD ABU KASHREEF

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

    اترك تعليقاً أو استفساراً