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

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

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

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

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

TypeScript Generics: دالة واحدة لكل الأنواع من غير any

📅 ١٩ أبريل ٢٠٢٦⏱ 4 دقائق قراءة
TypeScript Generics: دالة واحدة لكل الأنواع من غير any

لو عندك دالة بتاخد array وترجّع أول عنصر فيه، وعامل منها نسخة لـ string[] وتانية لـ number[] وتالتة لـ User[]، أنت بتعيد نفس اللوجيك 3 مرات. Generics في TypeScript بتخليك تكتبها مرة واحدة مع الحفاظ على الـ type بالظبط لكل نداء.

TypeScript Generics من غير لف

المشكلة باختصار

فيه طريقتين شائعتين للتعامل مع دوال بتشتغل على أكتر من نوع، وكلاهما بيجيب مشاكل في production:

  • تكرار الدالة لكل نوع: كود ميت وصيانة مكلّفة.
  • استخدام any: بتفقد كل فوائد TypeScript وبترجع لحالة JavaScript النقية.

Generic param هو ببساطة نوع متغيّر بيتحدّد وقت النداء. نفس الفكرة زي بارامتر عادي في دالة، بس بتمرّر فيه نوع بدل قيمة.

محرر أكواد يعرض تعريف دالة TypeScript مع Generics

أول مثال: دالة identity

الصيغة الأساسية: حرف واحد (العُرف T) بين <> بعد اسم الدالة.

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

const a = identity(42);        // a: number
const b = identity("hello");   // b: string
const c = identity([1, 2, 3]); // c: number[]

ركّز على حاجة مهمة: TypeScript استنتج T تلقائيًا من الـ argument. مش لازم تكتب identity<number>(42) إلا لو الاستنتاج فشل أو مش واضح.

مثال أقرب للشغل اليومي: getFirst

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

interface User { id: string; name: string; }

const users: User[] = [{ id: "1", name: "Ahmed" }];
const first = getFirst(users); // first: User | undefined

console.log(first?.name); // TypeScript عارف إن فيه name

قارنها بالإصدار بـ any:

TypeScript
function getFirstBad(items: any[]): any {
  return items[0];
}

const first = getFirstBad(users);
first.nammmme; // لا خطأ compile، bug في runtime

الفرق مش تجميلي. الإصدار الأول بيمسك الـ typo وقت الكتابة، التاني بيكسر في الـ browser عند المستخدم.

Constraints: لما تحتاج تضمن إن النوع فيه خاصية معيّنة

أحيانًا الدالة محتاجة تشتغل على أي نوع، بس بشرط إن النوع ده يحتوي على خاصية. استخدم extends.

TypeScript
interface HasId { id: string; }

function findById<T extends HasId>(items: T[], id: string): T | undefined {
  return items.find(item => item.id === id);
}

findById(users, "1"); // شغّال لأن User فيه id
findById([{ name: "x" }], "1"); // خطأ compile: مفيش id

الفايدة: الدالة مرنة (بتشتغل على User، Product، Order... أي نوع فيه id)، مع الحفاظ على الـ return type الحقيقي. مش مجرد HasId، ده T الكامل بكل خصائصه.

شاشة لابتوب تعرض كود TypeScript مع type constraints

أكتر من Generic في نفس الدالة

TypeScript
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
  const result = {} as Pick<T, K>;
  for (const key of keys) result[key] = obj[key];
  return result;
}

const user = { id: "1", name: "Ahmed", email: "a@b.com" };
const slim = pick(user, ["id", "name"]);
// slim: { id: string; name: string }
// slim.email // خطأ compile: مش موجود

دي دالة pick كاملة بـ 4 سطور، type-safe تمامًا. لو جربت تعمل نفس الحاجة بـ any أو بدون generics هتلاقي نفسك بتفقد المعلومة عن الـ keys المختارة.

سيناريو واقعي: client بيضرب REST API

TypeScript
async function apiGet<T>(url: string): Promise<T> {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json() as Promise<T>;
}

interface Product { id: string; price: number; }

const product = await apiGet<Product>("/api/products/1");
console.log(product.price); // TypeScript يعرف النوع

هنا Generic بتخلّي دالة واحدة تخدم كل الـ endpoints. بس لاحظ الـ trade-off: الـ as Promise<T> كذبة للـ compiler. في runtime مفيش ضمان إن الـ API رجّع فعلًا الشكل ده. لو عندك حالات حرجة استخدم zod أو io-ts للـ validation الفعلي.

trade-offs لازم تعرفها

  • المكسب: إعادة استخدام بدون فقدان الـ type، اكتشاف أخطاء وقت الكتابة، IntelliSense بيظهر الخصائص الحقيقية بعد النداء.
  • التكلفة: الصياغة بتبقى أصعب للقراءة لما تتداخل (<T, K extends keyof T, V extends T[K]>). فريق مبتدئ في TS بيتعثر فيها.
  • الافتراض: الشرح ده مبني على TypeScript 4.7+. فيه تحسينات على الاستنتاج في الإصدارات الأحدث بتقلّل عدد المرات اللي بتحتاج فيها تكتب الـ generic يدويًا.

متى لا تستخدم Generics

Generic مش هدف في حد ذاته. لا تستخدمه في الحالات دي:

  • الدالة شغّالة على نوع واحد بس ومفيش نية لتوسيع. اكتب النوع صراحةً أوضح.
  • الـ generic param ظهر مرة واحدة في الـ signature ومش راجع في الـ output. غالبًا ده علامة code smell: النوع ملوش لازمة.
  • بتكتب مكتبة utility داخلية صغيرة فيها 10 دوال generic متداخلة. ممكن تبسّطها بـ union types بدل ما تستعرض قدرات الـ type system.

قياس تقريبي من مشاريع متوسطة: لو أكتر من 40% من دوالك generic، ده مؤشر إنك بتعمل abstraction مبكّرة.

الخطوة التالية

افتح أقرب ملف .ts فيه any عندك وشوف أول 3 حالات. لو أي حالة منهم في signature دالة ومش boundary خارجي (زي JSON input غير موثوق)، حوّلها لـ generic. لو الـ compile فضل شغّال والـ tests عدّت، كسبت type safety ببلاش. لو وقعت، المكان ده كان بالظبط فيه bug محتمل مختبّي.

المصادر

  • TypeScript Handbook — Generics
  • TypeScript Handbook — Utility Types (Pick, Partial, Record)
  • Microsoft TypeScript Wiki — Generics FAQ
  • MDN — Fetch API

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة