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<T>(x: T): T</code></td>
<td>دالة عامة</td>
</tr>
<tr>
<td><code>interface X<T></code></td>
<td>واجهة عامة</td>
</tr>
<tr>
<td><code>class X<T></code></td>
<td>فئة عامة</td>
</tr>
<tr>
<td><code><T extends U></code></td>
<td>قيد على النوع</td>
</tr>
<tr>
<td><code>keyof T</code></td>
<td>مفاتيح النوع</td>
</tr>
<tr>
<td><code>Partial<T></code></td>
<td>جميع الخصائص اختيارية</td>
</tr>
<tr>
<td><code>Required<T></code></td>
<td>جميع الخصائص مطلوبة</td>
</tr>
<tr>
<td><code>Pick<T, K></code></td>
<td>اختيار خصائص</td>
</tr>
<tr>
<td><code>Omit<T, K></code></td>
<td>حذف خصائص</td>
</tr>
<tr>
<td><code>Record<K, V></code></td>
<td>كائن بمفاتيح/قيم</td>
</tr>
<tr>
<td><code>Readonly<T></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 قوية حقاً. أتقنها جيداً، وستتمكن من بناء مكتبات وأطر عمل.
**هل أتقنت الأدوية؟ شاركنا في التعليقات!**