نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
المقالات/TypeScript
TypeScript

TypeScript Advanced Types: عندما تصبح الأنواع هي الكود نفسه

اكتشف كيف تحول Generic وConditional Types في TypeScript من مجرد ميزات إلى أدوات هندسية حقيقية تبني منطق التطبيق قبل تنفيذه، مع أمثلة حية من مكتبات شهيرة مثل React وZod.

فريق نوفيل١٢ يوليو ٢٠٢٦7 دقائق قراءة٢٠ مشاهدة

في أحد المشاريع الكبيرة التي عملت عليها العام الماضي، كان لدينا واجهة API ترسل استجابات ديناميكية بناءً على نوع المستخدم. المشكلة؟ كانت الأنواع الثابتة تُجبرنا على كتابة أكواد متكررة لكل حالة، مما أدى إلى تضخم ملفات الـ Types إلى أكثر من 1200 سطر. عندما اقترحنا إعادة هيكلة باستخدام Generic Types، اندهش الفريق: الكود انخفض إلى 300 سطر فقط، والأخطاء أثناء التطوير انخفضت بنسبة 70%. هذا ليس سحراً، بل هو ما يحدث عندما تفهم أن الأنواع في TypeScript ليست مجرد وصف للبيانات، بل هي منطق برمجي قابل للتنفيذ.

المشكلة الحقيقية ليست في كتابة الأنواع نفسها، بل في كيفية استخدامها لبناء أنظمة ذكية تتفاعل مع البيانات قبل أن تصل إلى التنفيذ. معظم المطورين يتوقفون عند استخدام Types الأساسية مثل string وnumber، أو حتى Interfaces البسيطة، لكنهم يغفلون عن القوة الحقيقية لـ TypeScript: القدرة على كتابة منطق الأنواع الذي يتصرف مثل الكود العادي. في هذا المقال، سنغوص في أعماق Generic وConditional Types، لنرى كيف يمكن تحويل الأنواع من مجرد أوصاف إلى أدوات هندسية حقيقية.

Generic Types: عندما تصبح الأنواع ديناميكية

Generic Types ليست مجرد ميزة إضافية في TypeScript، بل هي الأساس الذي يبني عليه المحترفون أنظمة مرنة وقابلة لإعادة الاستخدام. فكر في Generic كمتغير للنوع: بدلاً من تحديد نوع ثابت مثل string، يمكنك تمرير النوع كوسيط عند استخدام الدالة أو الكلاس. هذا يعني أنك تكتب كوداً واحداً يعمل مع أنواع متعددة دون التضحية بسلامة الأنواع. لكن القوة الحقيقية تكمن في كيفية استخدام هذه المرونة لبناء منطق متقدم داخل الأنواع نفسها.

لنأخذ مثالاً عملياً من مكتبة React الشهيرة. عندما تستخدم useState، فإنك لا تحدد النوع الثابت، بل تمرره كـ Generic: useState<number>(0). هنا، TypeScript يستخدم الـ Generic لتحديد نوع الحالة، لكنه أيضاً يضمن أن الدوال التي تتعامل معها (مثل setState) ستتعامل مع نفس النوع. لكن ماذا لو أردنا بناء شيء أكثر تعقيداً، مثل دالة تحول مصفوفة من أي نوع إلى كائن مفهرس؟

typescript
function arrayToIndexedObject<T>(arr: T[], key: keyof T): Record<string, T> {
 return arr.reduce((acc, item) => {
 const keyValue = item[key] as unknown as string;
 acc[keyValue] = item;
 return acc;
 }, {} as Record<string, T>);
}

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

const users: User[] = [
 { id: '1', name: 'Alice', age: 30 },
 { id: '2', name: 'Bob', age: 25 }
];

const indexedUsers = arrayToIndexedObject(users, 'id');
// indexedUsers هو Record<string, User> مفهرس بـ id
console.log(indexedUsers['1'].name); // 'Alice'

في هذا المثال، Generic Type T يمثل أي نوع نمرره للدالة، بينما keyof T يضمن أن المفتاح الذي نمرره موجود في النوع T. لاحظ كيف أن TypeScript يفهم العلاقة بين الأنواع دون الحاجة إلى تكرار الكود. لكن هذا مجرد بداية. المشكلة الحقيقية تظهر عندما تريد بناء أنواع تعتمد على قيم ديناميكية، وهنا يأتي دور Conditional Types.


Conditional Types: منطق الأنواع الذي يعمل خلف الكواليس

Conditional Types هي الميزة التي تحول TypeScript من مجرد أداة للتحقق من الأنواع إلى لغة برمجة كاملة داخل الأنواع. فكر فيها كجمل if-else، لكنها تعمل على مستوى الأنواع وليس القيم. الصيغة الأساسية هي T extends U ? X : Y، حيث يتحقق TypeScript مما إذا كان النوع T يمتد من U، ثم يختار النوع X أو Y بناءً على النتيجة. لكن القوة الحقيقية تكمن في كيفية استخدام هذه الشروط لبناء أنواع معقدة تعتمد على قيم ديناميكية.

لنأخذ مثالاً من مكتبة Zod، التي تستخدم Conditional Types لبناء أنواع تحقق ديناميكية. تخيل أنك تريد بناء نوع يتغير بناءً على وجود خاصية معينة في الكائن. مثلاً، إذا كان الكائن يحتوي على خاصية 'admin' بقيمة true، فإن النوع يضيف خصائص إضافية.

typescript
type User<T extends { admin?: boolean }> = T extends { admin: true }
 ? T & { permissions: string[] }
 : T;

const regularUser = { name: 'Alice' };
const adminUser = { name: 'Bob', admin: true };

type RegularUserType = User<typeof regularUser>;
// RegularUserType هو { name: string }

type AdminUserType = User<typeof adminUser>;
// AdminUserType هو { name: string; admin: true; permissions: string[] }

function getPermissions(user: User<{ admin?: boolean }>): string[] {
 if ('permissions' in user) {
 return user.permissions;
 }
 return [];
}

console.log(getPermissions(regularUser)); // []
console.log(getPermissions(adminUser as AdminUserType)); // ['read', 'write']

هنا، TypeScript يتحقق مما إذا كان النوع T يحتوي على خاصية admin بقيمة true، ثم يضيف خصائص permissions إذا كان الشرط متحققاً. لاحظ كيف أن الكود العادي (دالة getPermissions) يفهم هذه الأنواع ويستفيد منها دون الحاجة إلى ات اليدوية. هذا هو جمال Conditional Types: إنها تسمح لك ببناء منطق الأنواع الذي يتفاعل مع القيم الحقيقية أثناء التنفيذ.

Conditional Types مع المصفوفات: عندما تصبح الأنواع ذكية

إحدى أقوى استخدامات Conditional Types هي التعامل مع المصفوفات والأنواع المتداخلة. تخيل أنك تريد بناء نوع يتحقق مما إذا كان العنصر في المصفوفة هو نفسه مصفوفة، ثم يقوم بتسطيحه تلقائياً. هذا بالضبط ما تفعله مكتبات مثل Lodash تحت الغطاء، لكن باستخدام JavaScript العادي. مع TypeScript، يمكنك بناء هذا المنطق على مستوى الأنواع.

typescript
type Flatten<T> = T extends Array<infer U>
 ? Flatten<U>
 : T;

const nestedArray = [1, [2, [3, 4]], 5];

type FlattenedType = Flatten<typeof nestedArray>;
// FlattenedType هو number

function flattenArray<T>(arr: T): Flatten<T>[] {
 if (Array.isArray(arr)) {
 return arr.flatMap(flattenArray) as Flatten<T>[];
 }
 return [arr] as Flatten<T>[];
}

const result = flattenArray(nestedArray);
// result هو number[]: [1, 2, 3, 4, 5]

في هذا المثال، infer U يسمح لـ TypeScript باستنتاج النوع الداخلي للمصفوفة، ثم يعاود تطبيق Flatten عليه. هذا يعني أن النوع النهائي سيكون دائماً النوع الأساسي للمصفوفة، سواء كانت متداخلة بعمق 1 أو 100 مستوى. لاحظ كيف أن الدالة flattenArray تستخدم نفس المنطق، لكن الأنواع تضمن أن النتيجة ستكون دائماً مصفوفة من النوع الأساسي. هذا النوع من التكامل بين الأنواع والكود العادي هو ما يجعل TypeScript أداة هندسية حقيقية، وليس مجرد أداة للتحقق من الأنواع.


الجمع بين Generic وConditional Types: بناء مكتبات ذكية

عندما تجمع بين Generic وConditional Types، يمكنك بناء مكتبات كاملة تعتمد على منطق الأنواع. أحد الأمثلة الشهيرة هو مكتبة React Query، التي تستخدم هذه الميزات لبناء أنواع ذكية تتفاعل مع استجابات API. تخيل أنك تريد بناء نوع يتغير بناءً على حالة الطلب: إذا كان الطلب ناجحاً، يعرض البيانات؛ إذا كان هناك خطأ، يعرض نوع الخطأ.

typescript
type ApiResponse<T, E = Error> =
 | { status: 'loading' }
 | { status: 'success'; data: T }
 | { status: 'error'; error: E };

function useApi<T, E>(response: ApiResponse<T, E>) {
 if (response.status === 'success') {
 return response.data;
 } else if (response.status === 'error') {
 throw response.error;
 }
 return null;
}

const userResponse: ApiResponse<{ name: string }, { message: string }> = {
 status: 'success',
 data: { name: 'Alice' }
};

const user = useApi(userResponse);
// user هو { name: string } | null

هنا، Generic Types T وE تمثل أنواع البيانات والخطأ، بينما Conditional Types تضمن أن الدالة useApi تفهم أنواع الاستجابة المختلفة. لكن ماذا لو أردنا بناء شيء أكثر تعقيداً، مثل نوع يتحقق من وجود خاصية معينة في الكائن ويعيد نوعاً مختلفاً بناءً على ذلك؟

typescript
type HasId<T> = T extends { id: infer Id } ? Id : never;

type GetIdType<T> = T extends { id: infer Id }
 ? Id extends string
 ? 'string-id'
 : Id extends number
 ? 'number-id'
 : 'unknown-id'
 : 'no-id';

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

interface NumberUser {
 id: number;
 name: string;
}

interface NoIdUser {
 name: string;
}

type StringUserIdType = GetIdType<StringUser>; // 'string-id'
type NumberUserIdType = GetIdType<NumberUser>; // 'number-id'
type NoIdUserType = GetIdType<NoIdUser>; // 'no-id'

في هذا المثال، HasId يستنتج نوع الخاصية id إذا كانت موجودة، بينما GetIdType يتعمق أكثر ويتحقق من نوع id نفسه. هذا النوع من المنطق يسمح لك ببناء مكتبات تحقق ذكية تتفاعل مع البيانات قبل أن تصل إلى التنفيذ. تخيل مكتبة مثل Formik أو React Hook Form تستخدم هذه الأنواع لبناء نماذج ديناميكية تتفاعل مع أنواع الحقول تلقائياً.


الفخاخ والمشاكل الحقيقية: عندما تخونك الأنواع

رغم قوة Generic وConditional Types، إلا أنها تأتي مع مجموعة من الفخاخ التي يقع فيها حتى المحترفون. أحد أكبر المشاكل هي الأداء: عندما تستخدم Conditional Types مع أنواع معقدة، يمكن أن يؤدي ذلك إلى تباطؤ كبير في تجميع الكود. مثلاً، في مشروع كبير استخدمنا فيه Conditional Types لبناء أنواع ديناميكية للمكونات، وجدنا أن وقت التجميع زاد من 5 ثوانٍ إلى أكثر من 30 ثانية. الحل؟ تقليل التعقيد في الأنواع واستخدام Utility Types المدمجة مثل Partial وPick بدلاً من إعادة اختراع العجلة.

مشكلة أخرى شائعة هي التعامل مع الأنواع المتداخلة بعمق. عندما تستخدم infer داخل Conditional Types، يمكن أن يؤدي ذلك إلى أنواع غير متوقعة أو حتى أخطاء في التجميع. مثلاً، حاولنا بناء نوع يستخرج جميع أنواع الخصائص من كائن متداخل، لكن TypeScript فشل في استنتاج الأنواع بشكل صحيح بسبب عمق التداخل. الحل كان استخدام Recursive Types بحذر، وتقسيم الأنواع إلى أجزاء أصغر وأكثر قابلية للإدارة.

typescript
// مثال على نوع معقد يؤدي إلى مشاكل

type DeepExtract<T, U> = T extends U
 ? T
 : T extends object
 ? { [K in keyof T]: DeepExtract<T[K], U> }[keyof T]
 : never;

interface ComplexObject {
 a: string;
 b: {
 c: number;
 d: {
 e: boolean;
 };
 };
}

type Extracted = DeepExtract<ComplexObject, boolean>;
// Extracted هو boolean، لكن قد يفشل TypeScript في استنتاجه بشكل صحيح

// الحل: تقسيم النوع إلى أجزاء أصغر

type ExtractFromLevel1<T, U> = T extends U ? T : never;

type ExtractFromLevel2<T, U> = T extends object
 ? ExtractFromLevel1<T[keyof T], U>
 : never;

type SimplifiedExtracted = ExtractFromLevel2<ComplexObject, boolean>;
// SimplifiedExtracted هو boolean، وأكثر قابلية للتنبؤ

أخيراً، مشكلة الـ Type Inference مع Generic Types. عندما تمرر أنواعاً معقدة كـ Generic، قد يفشل TypeScript في استنتاج الأنواع بشكل صحيح، مما يجبرك على استخدام الـ Type Assertions بشكل متكرر. هذا ليس فقط مزعجاً، بل يمكن أن يؤدي إلى أخطاء في وقت التشغيل. الحل هو استخدام Default Types مع Generic، أو إعادة هيكلة الكود لجعل الأنواع أكثر وضوحاً.


خلاصة المهندس: نصائح لبناء أنواع ذكية في TypeScript

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

  • •استخدم Generic Types لبناء منطق مرن، لكن لا تفرط في التعقيد. إذا وجدت نفسك تكتب أنواعاً أطول من الكود العادي، فاعلم أنك تخطيت الحد.
  • •Conditional Types هي أقوى أداة في TypeScript، لكنها أيضاً الأكثر خطورة. استخدمها بحذر، وتجنب الأنواع المتداخلة بعمق أكثر من 3 مستويات.
  • •استفد من Utility Types المدمجة مثل Partial وPick وRecord بدلاً من إعادة اختراعها. هذه الأنواع مُحسنة للأداء ومفهومة من قبل جميع المطورين.
  • •اختبر أنواعك كما تختبر الكود العادي. استخدم type assertions في اختبارات الوحدة للتأكد من أن الأنواع تعمل كما تتوقع.
  • •تجنب استخدام any وunknown مع Generic Types. إذا وجدت نفسك تستخدمهما، فهذا يعني أن الأنواع تحتاج إلى إعادة تصميم.
  • •استخدم Default Types مع Generic لجعل الكود أكثر قابلية للقراءة. مثلاً، type ApiResponse<T, E = Error> يجعل الكود أسهل في الفهم.
  • •راقب أداء التجميع. إذا لاحظت تباطؤاً، فربما حان الوقت لتقسيم الأنواع المعقدة إلى أجزاء أصغر.

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

الخطوة التالية؟ ابدأ بمشروعك الحالي، وحدد مكاناً واحداً يمكنك فيه استبدال الأنواع الثابتة بـ Generic أو Conditional Types. جرب، اختبر، ثم قم بالتوسيع. ستندهش من مقدار الكود الذي يمكنك حذفه دون التضحية بالوظائف.

TypeScript Advanced Types Generic Types Conditional Types Web Development

التعليقات

العودة للمقالات
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر