TypeScript

الأدوية (Generics) في TypeScript — دليل شامل 2026

📅 2026-10-06⏱ 9 دقائق قراءة
في المقال السابق، تعلمت الفئات (Classes) والبرمجة كائنية التوجه. الآن سنتعمق في **الأدوية (Generics)** — وهي إحدى أقوى ميزات TypeScript لكتابة كود **مرن وآمن** في نفس الوقت. في هذا الدليل العملي، سنأخذك خطوة بخطوة لفهم الأدوية، مع تمارين وحلول. ## ما هي الأدوية (Generics)؟ **Generics** هي طريقة لكتابة كود يعمل مع **أنواع متعددة** دون التضحية بالأمان. **تشبيه بسيط:** تخيل أن لديك **صندوقاً** يمكنه حمل أي شيء. في JavaScript، الصندوق هو `any`. في TypeScript مع Generics، الصندوق **يحتفظ بنوع ما بداخله**. **مثال بسيط:** ```typescript function identity<T>(value: T): T { return value; } console.log(identity<string>("أحمد")); // أحمد console.log(identity<number>(25)); // 25 console.log(identity<boolean>(true)); // true // TypeScript تستنتج النوع تلقائياً: console.log(identity("أحمد")); // أحمد ``` **شرح:** - **`<T>`:** نوع عام (Type Parameter). `T` هو اسم عرفي (يمكن أن يكون أي شيء). - **`value: T`:** المعامل من النوع `T`. - **`: T`:** القيمة المرجعة من النوع `T`. ## لماذا نحتاج الأدوية؟ بدون Generics: ```typescript function identityString(value: string): string { return value; } function identityNumber(value: number): number { return value; } function identityBoolean(value: boolean): boolean { return value; } // ... إلخ ``` مع Generics: ```typescript function identity<T>(value: T): T { return value; } ``` **الفوائد:** - **إعادة الاستخدام:** دالة واحدة لكل الأنواع. - **الأمان:** TypeScript تعرف النوع الدقيق. - **الوضوح:** الكود يوثق نفسه. - **الأداء:** لا حاجة لـ `any`. ## الأدوية في الدوال ### 1. دالة بسيطة ```typescript function identity<T>(value: T): T { return value; } console.log(identity<string>("أحمد")); // أحمد console.log(identity<number>(25)); // 25 ``` ### 2. دالة تأخذ عدة أنواع عامة ```typescript function pair<T, U>(first: T, second: U): [T, U] { return [first, second]; } console.log(pair<string, number>("أحمد", 25)); // ["أحمد", 25] console.log(pair("أحمد", 25)); // TypeScript تستنتج ``` ### 3. دالة تعمل مع المصفوفات ```typescript function getFirst<T>(arr: T[]): T | undefined { return arr[0]; } console.log(getFirst([1, 2, 3])); // 1 console.log(getFirst(["أ", "ب", "ج"])); // أ console.log(getFirst([])); // undefined ``` ## الأدوية في الواجهات ```typescript interface Box<T> { value: T; } const numberBox: Box<number> = { value: 25 }; const stringBox: Box<string> = { value: "أحمد" }; const boolBox: Box<boolean> = { value: true }; ``` **مثال عملي: ApiResponse** ```typescript interface ApiResponse<T> { success: boolean; data: T; message?: string; } interface User { id: number; name: string; } const response: ApiResponse<User> = { success: true, data: { id: 1, name: "أحمد" }, message: "تم بنجاح", }; console.log(response.data.name); // أحمد ``` ## الأدوية في الفئات ```typescript class Stack<T> { private items: T[] = []; push(item: T): void { this.items.push(item); } pop(): T | undefined { return this.items.pop(); } peek(): T | undefined { return this.items[this.items.length - 1]; } get size(): number { return this.items.length; } } const numberStack = new Stack<number>(); numberStack.push(1); numberStack.push(2); console.log(numberStack.pop()); // 2 const stringStack = new Stack<string>(); stringStack.push("أحمد"); console.log(stringStack.pop()); // أحمد ``` ## القيود (Constraints) أحياناً تريد تقييد الأنواع المسموح بها: ### 1. استخدام `extends` ```typescript function getLength<T extends { length: number }>(value: T): number { return value.length; } console.log(getLength("أحمد")); // 4 console.log(getLength([1, 2, 3])); // 3 // console.log(getLength(25)); // ❌ خطأ: number ليس له length ``` **الفائدة:** تضمن أن النوع يحتوي على `length`. ### 2. قيود متعددة ```typescript interface HasId { id: number; } interface HasName { name: string; } function printUser<T extends HasId & HasName>(user: T): void { console.log(`${user.id}: ${user.name}`); } printUser({ id: 1, name: "أحمد", email: "ahmed@example.com" }); ``` ### 3. استخدام `keyof` ```typescript function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const person = { name: "أحمد", age: 25 }; console.log(getProperty(person, "name")); // أحمد console.log(getProperty(person, "age")); // 25 // getProperty(person, "email"); // ❌ خطأ ``` ## القيم الافتراضية للأدوية ```typescript interface Box<T = string> { value: T; } const defaultBox: Box = { value: "أحمد" }; // T = string const numberBox: Box<number> = { value: 25 }; // T = number ``` ## دالة map عامة ```typescript function map<T, U>(arr: T[], fn: (item: T) => U): U[] { return arr.map(fn); } const numbers = [1, 2, 3, 4]; const doubled = map(numbers, (n) => n * 2); console.log(doubled); // [2, 4, 6, 8] const names = ["أحمد", "محمد"]; const lengths = map(names, (name) => name.length); console.log(lengths); // [4, 4] ``` ## Generic Utility: `Partial<T>` `Partial<T>` يجعل كل خصائص `T` اختيارية: ```typescript interface User { id: number; name: string; email: string; } function updateUser(id: number, updates: Partial<User>): User { const user: User = { id, name: "", email: "" }; return { ...user, ...updates }; } console.log(updateUser(1, { name: "أحمد" })); // { id: 1, name: "أحمد", email: "" } ``` ## Generic Utility: `Required<T>` `Required<T>` يجعل كل الخصائص مطلوبة: ```typescript interface Config { apiKey?: string; url?: string; } function setup(config: Required<Config>): void { console.log(config.apiKey, config.url); } setup({ apiKey: "abc", url: "https://api.example.com" }); ``` ## Generic Utility: `Pick<T, K>` `Pick<T, K>` يختار خصائص محددة: ```typescript interface User { id: number; name: string; email: string; password: string; } type PublicUser = Pick<User, "id" | "name" | "email">; const publicUser: PublicUser = { id: 1, name: "أحمد", email: "ahmed@example.com", }; ``` ## Generic Utility: `Omit<T, K>` `Omit<T, K>` يحذف خصائص محددة: ```typescript type UserWithoutPassword = Omit<User, "password">; const user: UserWithoutPassword = { id: 1, name: "أحمد", email: "ahmed@example.com", }; ``` ## Generic Utility: `Record<K, V>` `Record<K, V>` ينشئ كائن بمفاتيح وقيم محددة: ```typescript type Roles = "admin" | "user" | "guest"; const permissions: Record<Roles, string[]> = { admin: ["read", "write", "delete"], user: ["read", "write"], guest: ["read"], }; ``` ## Generic Utility: `Readonly<T>` `Readonly<T>` يجعل كل الخصائص للقراءة فقط: ```typescript interface Config { apiKey: string; url: string; } const config: Readonly<Config> = { apiKey: "abc", url: "https://api.example.com", }; // config.apiKey = "new"; // ❌ خطأ ``` ## تمارين عملية ### تمرين 1: دالة identity اكتب دالة `identity<T>` ترجع نفس القيمة المدخلة. **الحل:** ```typescript function identity<T>(value: T): T { return value; } console.log(identity<string>("أحمد")); // أحمد console.log(identity<number>(25)); // 25 ``` ### تمرين 2: أول عنصر اكتب دالة ترجع أول عنصر من مصفوفة. **الحل:** ```typescript function getFirst<T>(arr: T[]): T | undefined { return arr[0]; } console.log(getFirst([1, 2, 3])); // 1 console.log(getFirst([])); // undefined ``` ### تمرين 3: عكس مصفوفة اكتب دالة تعكس مصفوفة من أي نوع. **الحل:** ```typescript function reverse<T>(arr: T[]): T[] { return [...arr].reverse(); } console.log(reverse([1, 2, 3])); // [3, 2, 1] console.log(reverse(["أ", "ب", "ج"])); // ["ج", "ب", "أ"] ``` ### تمرين 4: واجهة Pair عرّف واجهة `Pair<T, U>` لها `first` و `second`. **الحل:** ```typescript interface Pair<T, U> { first: T; second: U; } const pair: Pair<string, number> = { first: "أحمد", second: 25, }; console.log(pair.first); // أحمد console.log(pair.second); // 25 ``` ### تمرين 5: Stack عام أنشئ فئة `Stack<T>` مع push, pop, peek. **الحل:** ```typescript class Stack<T> { private items: T[] = []; push(item: T): void { this.items.push(item); } pop(): T | undefined { return this.items.pop(); } peek(): T | undefined { return this.items[this.items.length - 1]; } get size(): number { return this.items.length; } } const stack = new Stack<number>(); stack.push(1); stack.push(2); console.log(stack.peek()); // 2 console.log(stack.pop()); // 2 ``` ### تمرين 6: القيود اكتب دالة `getLength<T extends { length: number }>`. **الحل:** ```typescript function getLength<T extends { length: number }>(value: T): number { return value.length; } console.log(getLength("أحمد")); // 4 console.log(getLength([1, 2, 3])); // 3 ``` ### تمرين 7: Partial استخدم `Partial<User>` لتحديث مستخدم. **الحل:** ```typescript interface User { id: number; name: string; email: string; } function updateUser(id: number, updates: Partial<User>): Partial<User> { return { id, ...updates }; } console.log(updateUser(1, { name: "أحمد" })); // { id: 1, name: "أحمد" } ``` ### تمرين 8: Pick و Omit استخدم `Pick` و `Omit` لإنشاء أنواع مشتقة. **الحل:** ```typescript interface User { id: number; name: string; email: string; password: string; } type PublicUser = Pick<User, "id" | "name" | "email">; type UserWithoutPassword = Omit<User, "password">; const publicUser: PublicUser = { id: 1, name: "أحمد", email: "ahmed@example.com", }; ``` ## حل المشاكل الشائعة ### 🔴 المشكلة 1: `Type 'X' is not assignable to type 'Y'` **السبب:** استخدمت نوعاً لا يحقق القيود. ```typescript function getLength<T extends { length: number }>(value: T): number { return value.length; } getLength(25); // ❌ number ليس له length ``` **الحل:** استخدم نوعاً يحقق القيود (string, array, ...). ### 🔴 المشكلة 2: `T could be instantiated with an arbitrary type` **السبب:** حاولت الوصول لخاصية غير موجودة في `T`. ```typescript function getId<T>(obj: T): number { return obj.id; // ❌ T ليس له id } ``` **الحل:** استخدم قيداً: ```typescript function getId<T extends { id: number }>(obj: T): number { return obj.id; // ✅ } ``` ### 🔴 المشكلة 3: `Argument of type 'X' is not assignable to parameter of type 'Y'` **السبب:** النوع المستنتج مختلف عن المطلوب. **الحل:** حدد النوع يدوياً: ```typescript const result = identity<string>("أحمد"); ``` ## جدول الأوامر الأساسية <table> <thead> <tr> <th>الأمر</th> <th>الوظيفة</th> </tr> </thead> <tbody> <tr> <td><code>function f&lt;T&gt;(x: T): T</code></td> <td>دالة عامة</td> </tr> <tr> <td><code>interface X&lt;T&gt;</code></td> <td>واجهة عامة</td> </tr> <tr> <td><code>class X&lt;T&gt;</code></td> <td>فئة عامة</td> </tr> <tr> <td><code>&lt;T extends U&gt;</code></td> <td>قيد على النوع</td> </tr> <tr> <td><code>keyof T</code></td> <td>مفاتيح النوع</td> </tr> <tr> <td><code>Partial&lt;T&gt;</code></td> <td>جميع الخصائص اختيارية</td> </tr> <tr> <td><code>Required&lt;T&gt;</code></td> <td>جميع الخصائص مطلوبة</td> </tr> <tr> <td><code>Pick&lt;T, K&gt;</code></td> <td>اختيار خصائص</td> </tr> <tr> <td><code>Omit&lt;T, K&gt;</code></td> <td>حذف خصائص</td> </tr> <tr> <td><code>Record&lt;K, V&gt;</code></td> <td>كائن بمفاتيح/قيم</td> </tr> <tr> <td><code>Readonly&lt;T&gt;</code></td> <td>للقراءة فقط</td> </tr> </tbody> </table> ## قائمة تحقق نهائية <table> <thead> <tr> <th>المهمة</th> <th>الحالة</th> </tr> </thead> <tbody> <tr> <td>فهم الأدوية (Generics)</td> <td>⬜</td> </tr> <tr> <td>دوال عامة</td> <td>⬜</td> </tr> <tr> <td>واجهات عامة</td> <td>⬜</td> </tr> <tr> <td>فئات عامة</td> <td>⬜</td> </tr> <tr> <td>القيود (Constraints)</td> <td>⬜</td> </tr> <tr> <td>Utility Types (Partial, Pick, Omit, ...)</td> <td>⬜</td> </tr> <tr> <td>حل التمارين الثمانية</td> <td>⬜</td> </tr> </tbody> </table> ## ماذا بعد هذا المقال؟ الآن بعد أن أتقنت الأدوية، أنت جاهز للمقال التالي: 1. **الأنواع المتقدمة** — Union, Intersection, Conditional Types. 2. **Utility Types المتقدمة** — Exclude, Extract, ReturnType. 3. **مشروع React** — تطبيق عملي. ## الخلاصة في هذا المقال، تعلمت: - ✅ ما هي الأدوية ولماذا نحتاجها. - ✅ الأدوية في الدوال والواجهات والفئات. - ✅ القيود (Constraints) باستخدام `extends`. - ✅ `keyof` والوصول الآمن للخصائص. - ✅ Utility Types: `Partial`, `Required`, `Pick`, `Omit`, `Record`, `Readonly`. - ✅ كتابة كود مرن وآمن. **تذكر:** الأدوية هي ما يجعل TypeScript قوية حقاً. أتقنها جيداً، وستتمكن من بناء مكتبات وأطر عمل. **هل أتقنت الأدوية؟ شاركنا في التعليقات!**