المستوى المطلوب: متوسط — يفترض إنك مرتاح مع TypeScript الأساسي وعارف الفرق بين type و interface.
لو بتكتب نفس دالة fetchData تلات مرات — مرة للـ users، مرة للـ products، مرة للـ orders — وكل واحدة بترجّع Promise<any>، انت بتفقد 90% من قيمة TypeScript أصلاً. Generics في 8 سطور بتدمج الـ 3 دوال في واحدة، وبتحافظ على الـ type 100% من نقطة الـ call لحد آخر property بتقراها.
المشكلة باختصار
تطبيقك فيه 14 دالة بتتكلم مع REST API. كل واحدة بنفس الـ structure: fetch(), .json(), return. الكود مكرر، والأسوأ إن نص الدوال راجعة Promise<any> لإن المبرمج كسل يكتب الـ interface.
النتيجة: user.emial (طباعة غلط لـ email) ميرجعش error في الـ compile. الـ TypeScript ساكت لإن any معناها "أي حاجة جايزة". الـ bug بيظهر في الإنتاج لما المستخدم بيفتح صفحته ويلاقي undefined.
ما هو Generic بالظبط
تخيل عندك صندوق pizza. الصندوق بياخد أي حاجة جواه: Margherita، Hawaii، أو حتى ساندوتش. لكن لو كتبت على الصندوق "المحتوى: شيء"، الـ delivery boy مش هيعرف يدّي العميل الصح. ولو كتبت "المحتوى: Margherita" على كل الصناديق، الصندوق ده ما يصلحش لـ Hawaii.
الحل: تخلّي الصندوق نفسه يقول "محتواي = X"، و X بتتحدد ساعة ما تعبّي الصندوق. هي دي فكرة الـ Generic بالظبط.
تقنيًا، الـ Generic Type Parameter في TypeScript هو متغير من نوع type (مش متغير من نوع قيمة زي number). بدل ما تكتب نسخة من الدالة لكل نوع، بتكتب function<T>، والـ T placeholder بياخد قيمته ساعة الاستدعاء. الـ TypeScript Handbook بيسميها "Type Variables"، وبتشتغل على نفس فكرة الـ parametric polymorphism اللي ظهرت في ML سنة 1975.
الكود الواقعي قبل وبعد
قبل — مكرر و any:
async function fetchUser(id: number): Promise<any> {
const res = await fetch(`/api/users/${id}`);
return res.json();
}
async function fetchProduct(id: number): Promise<any> {
const res = await fetch(`/api/products/${id}`);
return res.json();
}
// نفس الكلام مكرر 12 مرة تانية…
بعد — Generic واحد:
async function fetchById<T>(endpoint: string, id: number): Promise<T> {
const res = await fetch(`/api/${endpoint}/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json() as Promise<T>;
}
interface User { id: number; name: string; email: string; }
interface Product { id: number; title: string; price: number; }
const user = await fetchById<User>('users', 1);
console.log(user.email); // شغّال، TypeScript عارف إن فيه email
console.log(user.price); // Compile error: Property 'price' does not exist on type 'User'