TypeScript📅 2026-10-06⏱ 9 دقائق قراءة📘 مقال 7 من 10

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

دليل عملي مفصل للأدوية في TypeScript — الدوال العامة، الواجهات العامة، القيود، مع تمارين وحلول

في المقال السابق، تعلمت الفئات (Classes) والبرمجة كائنية التوجه. الآن سنتعمق في الأدوية (Generics) — وهي إحدى أقوى ميزات TypeScript لكتابة كود مرن وآمن في نفس الوقت.

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

ما هي الأدوية (Generics)؟

Generics هي طريقة لكتابة كود يعمل مع أنواع متعددة دون التضحية بالأمان.

تشبيه بسيط: تخيل أن لديك صندوقاً يمكنه حمل أي شيء. في JavaScript، الصندوق هو any. في TypeScript مع Generics، الصندوق يحتفظ بنوع ما بداخله.

مثال بسيط:

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:

function identityString(value: string): string {
  return value;
}

function identityNumber(value: number): number {
  return value;
}

function identityBoolean(value: boolean): boolean {
  return value;
}
// ... إلخ

مع Generics:

function identity<T>(value: T): T {
  return value;
}

الفوائد:

  • إعادة الاستخدام: دالة واحدة لكل الأنواع.
  • الأمان: TypeScript تعرف النوع الدقيق.
  • الوضوح: الكود يوثق نفسه.
  • الأداء: لا حاجة لـ any.

الأدوية في الدوال

1. دالة بسيطة

function identity<T>(value: T): T {
  return value;
}

console.log(identity<string>("أحمد"));  // أحمد
console.log(identity<number>(25));      // 25

2. دالة تأخذ عدة أنواع عامة

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. دالة تعمل مع المصفوفات

function getFirst<T>(arr: T[]): T | undefined {
  return arr[0];
}

console.log(getFirst([1, 2, 3]));       // 1
console.log(getFirst(["أ", "ب", "ج"]));  // أ
console.log(getFirst([]));              // undefined

الأدوية في الواجهات

interface Box<T> {
  value: T;
}

const numberBox: Box<number> = { value: 25 };
const stringBox: Box<string> = { value: "أحمد" };
const boolBox: Box<boolean> = { value: true };

مثال عملي: ApiResponse

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);  // أحمد

الأدوية في الفئات

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

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. قيود متعددة

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: "[email protected]" });

3. استخدام keyof

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");  // ❌ خطأ

القيم الافتراضية للأدوية

interface Box<T = string> {
  value: T;
}

const defaultBox: Box = { value: "أحمد" };       // T = string
const numberBox: Box<number> = { value: 25 };    // T = number

دالة map عامة

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 اختيارية:

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> يجعل كل الخصائص مطلوبة:

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> يختار خصائص محددة:

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
}

type PublicUser = Pick<User, "id" | "name" | "email">;

const publicUser: PublicUser = {
  id: 1,
  name: "أحمد",
  email: "[email protected]",
};

Generic Utility: Omit<T, K>

Omit<T, K> يحذف خصائص محددة:

type UserWithoutPassword = Omit<User, "password">;

const user: UserWithoutPassword = {
  id: 1,
  name: "أحمد",
  email: "[email protected]",
};

Generic Utility: Record<K, V>

Record<K, V> ينشئ كائن بمفاتيح وقيم محددة:

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> يجعل كل الخصائص للقراءة فقط:

interface Config {
  apiKey: string;
  url: string;
}

const config: Readonly<Config> = {
  apiKey: "abc",
  url: "https://api.example.com",
};

// config.apiKey = "new";  // ❌ خطأ

تمارين عملية

تمرين 1: دالة identity

اكتب دالة identity<T> ترجع نفس القيمة المدخلة.

الحل:

function identity<T>(value: T): T {
  return value;
}

console.log(identity<string>("أحمد"));  // أحمد
console.log(identity<number>(25));      // 25

تمرين 2: أول عنصر

اكتب دالة ترجع أول عنصر من مصفوفة.

الحل:

function getFirst<T>(arr: T[]): T | undefined {
  return arr[0];
}

console.log(getFirst([1, 2, 3]));  // 1
console.log(getFirst([]));         // undefined

تمرين 3: عكس مصفوفة

اكتب دالة تعكس مصفوفة من أي نوع.

الحل:

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.

الحل:

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.

الحل:

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 }>.

الحل:

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> لتحديث مستخدم.

الحل:

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 لإنشاء أنواع مشتقة.

الحل:

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: "[email protected]",
};

حل المشاكل الشائعة

🔴 المشكلة 1: Type 'X' is not assignable to type 'Y'

السبب: استخدمت نوعاً لا يحقق القيود.

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.

function getId<T>(obj: T): number {
  return obj.id;  // ❌ T ليس له id
}

الحل: استخدم قيداً:

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'

السبب: النوع المستنتج مختلف عن المطلوب.

الحل: حدد النوع يدوياً:

const result = identity<string>("أحمد");

جدول الأوامر الأساسية

الأمرالوظيفة
function f<T>(x: T): Tدالة عامة
interface X<T>واجهة عامة
class X<T>فئة عامة
<T extends U>قيد على النوع
keyof Tمفاتيح النوع
Partial<T>جميع الخصائص اختيارية
Required<T>جميع الخصائص مطلوبة
Pick<T, K>اختيار خصائص
Omit<T, K>حذف خصائص
Record<K, V>كائن بمفاتيح/قيم
Readonly<T>للقراءة فقط

قائمة تحقق نهائية

المهمةالحالة
فهم الأدوية (Generics)⬜
دوال عامة⬜
واجهات عامة⬜
فئات عامة⬜
القيود (Constraints)⬜
Utility Types (Partial, Pick, Omit, ...)⬜
حل التمارين الثمانية⬜

ماذا بعد هذا المقال؟

الآن بعد أن أتقنت الأدوية، أنت جاهز للمقال التالي:

  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 قوية حقاً. أتقنها جيداً، وستتمكن من بناء مكتبات وأطر عمل.

هل أتقنت الأدوية؟ شاركنا في التعليقات!

📚 مقالات ذات صلة

TypeScript

مشروع Node.js مع TypeScript — API كامل 2026

مشروع عملي لبناء API كامل بـ Node.js و Express و TypeScript — خطوة بخطوة مع الكود الكامل والشرح

TypeScript

مشروع React مع TypeScript — تطبيق مهام كامل 2026

مشروع عملي لبناء تطبيق مهام (Todo App) بـ React و TypeScript — خطوة بخطوة مع الكود الكامل والشرح

TypeScript

الأنواع المتقدمة في TypeScript — دليل شامل 2026

دليل عملي مفصل للأنواع المتقدمة في TypeScript — Union, Intersection, Conditional, Mapped, Template Literal، مع تمارين وحلول