Menu
flag Ar iconالعربيةdown icon

الأنواع العامة (Generics) في TypeScript: دوال وأنواع وأصناف

الـ generics تتيح لدالة أو interface أو نوع أو صنف العمل مع أنواع كثيرة مع إبقائها مترابطة: ما يدخل يحدد ما يخرج. تعرّف على الدوال العامة، واستنتاج وسائط النوع، وتعدد معاملات النوع، والـ interfaces والأصناف العامة، والقيم الافتراضية، ومتى لا تستخدم الـ generics.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

الـ generics معاملات نوع. تعرّف الدالة العامة نوعًا نائبًا، يُسمى عادة T، ويملؤه كل استدعاء، فتحدد الأنواع الداخلة الأنواعَ الخارجة. عندها تعمل دالة واحدة مع الأرقام والنصوص وكائناتك الخاصة، ويبقى كل استدعاء محدد الأنواع بالكامل.

تعرّف <T> بعد اسم الدالة معامل النوع. يستخدمه items: T[] للمدخل وT | undefined للمخرج، وهذا ما يربط بينهما. لم تكتب number ولا string في أي مكان: استنتجت TypeScript النوع T من الوسيط.

لماذا لا any أو unknown؟

دون الـ generics كنت ستحدد نوع المعامل any أو unknown. كلاهما يقبل أي مصفوفة، وكلاهما يفقد الصلة بين المدخل والمخرج:

نوع المعامليقبل كل مدخلنوع النتيجةمفحوص
anyنعمanyلا: الأخطاء تُترجم
unknownنعمunknownنعم، لكن يجب أن تضيّقه قبل استخدامه
T (generic)نعممرتبط بالمدخلنعم، بالنوع الدقيق

استنتاج وسائط النوع

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

نتيجتان من نتائج الاستنتاج تستحقان المعرفة. pair(1, "one") دون الوسيط الصريح خطأ: تستنتج TypeScript T = number من الوسيط الأول ثم تبلّغ Argument of type 'string' is not assignable to parameter of type 'number'. (TS2345). وemptyList() دون أي وسيط تحصل على T = unknown، فتعطي unknown[]، وهذا نادرًا ما تريده.

عدة معاملات نوع

يمكن للدالة أن تعرّف عدد معاملات النوع الذي تحتاجه، مفصولة بفواصل. ويُستنتج كل منها مستقلًا عن الآخر.

تستنتج mapValues ثلاثة أشياء دفعة واحدة: K هو "tea" | "cake"، وV هو number، وR هو string من القيمة المُعادة من الـ callback. أما K extends string فقيد (constraint)، تشرحه الصفحة التالية.

الـ Interfaces والـ Type Aliases العامة

يمكن للأنواع أيضًا أن تأخذ معاملات. تكتب الوسيط حين تستخدم النوع: Box<number>، ApiResponse<User>. ومعظم أنواع المجموعات المدمجة تعمل بهذه الطريقة: Array<T>، وMap<K, V>، وPromise<T>، وRecord<K, V>.

يبيّن Result<T, E = string> أيضًا معامل نوع افتراضيًا: Result<number> يعني Result<number, string>. ومثل معاملات الدوال الاختيارية، يجب أن تأتي المعاملات ذات القيم الافتراضية بعد المعاملات المطلوبة.

الأصناف العامة

يأخذ الصنف معاملات النوع بعد اسمه، وكل نسخة تثبّتها. ويمكن للحقول والتوابع ومعاملات دالة البناء كلها أن تستخدمها.

إن كانت دالة البناء تأخذ T، يمكنك حذف الوسيط الصريح: new Box(5) يستنتج Box<number>. الأعضاء الثابتة تنتمي إلى الصنف نفسه لا إلى نسخة، فلا يمكنها استخدام معامل النوع الخاص بالصنف (Static members cannot reference class type parameters.، TS2302).

دوال الأسهم العامة وأنواع الدوال

تأتي قائمة معاملات النوع قبل قائمة المعاملات. والصيغة نفسها تصف نوع دالة عامة.

const last = <T>(items: T[]): T | undefined => items[items.length - 1];

type Mapper = <T, R>(items: T[], fn: (item: T) => R) => R[];
const mapAll: Mapper = (items, fn) => items.map(fn);

// In a .tsx file, <T> looks like a JSX tag. Add a trailing comma:
const lastTsx = <T,>(items: T[]) => items[items.length - 1];

استنتاج الأنواع الحرفية بمعاملات نوع const

افتراضيًا يستنتج الـ generic أنواعًا موسّعة: pair("a", "b") يعطي [string, string]. حين تهم القيم الدقيقة (أسماء المسارات، وأسماء الأحداث، وقوائم الأعمدة)، علّم المعامل بـ const (في TypeScript 5.0 وما بعده) فيُستنتج كأن الوسيط مكتوب بـ as const:

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

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

يستحق معامل النوع مكانه حين يربط بين شيئين: معامل ونوع القيمة المُعادة، أو معاملين، أو معامل وcallback. إن ظهر T مرة واحدة فقط فلا يضيف شيئًا، والنوع العادي أوضح.

// Pointless: T is used once, so it is just a longer way to write unknown
function logValue<T>(value: T): void {
  console.log(value);
}

// Clearer
function logValueSimple(value: unknown): void {
  console.log(value);
}

// Also pointless: returns T but nothing connects T to an argument,
// so the caller is really just asserting a type
function parseJson<T>(text: string): T {
  return JSON.parse(text);
}

الحالة الأخيرة شائعة ومضللة: يبدو parseJson<User>(text) آمن الأنواع لكنه لا يفحص شيئًا، تمامًا مثل JSON.parse(text) as User. أعد unknown وتحقق من البيانات بدلًا من ذلك. ومن علامات الإفراط الأخرى: معامل نوع يُعطى دائمًا الوسيط نفسه، أو معامل يمكن استبداله بـ union من نوعين معروفين.

للـ generics التي تحتاج إلى معرفة شيء عن T (أن له length، أو أن K مفتاح في T)، القيود بـ extends هي الخطوة التالية.

الأسئلة الشائعة

ما هي الـ generics في TypeScript؟

الـ generics معاملات نوع: عناصر نائبة مثل T تُملأ بنوع حقيقي في كل مرة تُستخدم فيها دالة أو interface أو type alias أو صنف. تعمل function first<T>(items: T[]): T | undefined مع أي مصفوفة، وتحمل النتيجة نوع عناصر المصفوفة التي مررتها، فيكون first([1, 2]) من النوع number | undefined وfirst(["a"]) من النوع string | undefined.

ما الفرق بين الـ generics وany؟

any يعطّل فحص الأنواع: لا علاقة بين القيمة الداخلة والقيمة الخارجة، والنتيجة any أيضًا. أما الـ generic فيحافظ على العلاقة: تعيد identity<T>(x: T): T النوع الذي مررته بالضبط. استخدم unknown إن كنت تقبل أي شيء ولا تحتاج إلى إعادة النوع، والـ generic إن كنت تحتاج إليه.

ماذا تعني <T> في TypeScript؟

تعرّف معامل نوع اسمه T. في function wrap<T>(value: T) يكون T متغير نوع تملؤه TypeScript من الوسيط في كل استدعاء، أو تمرره أنت صراحة بصيغة wrap<string>("a"). الاسم T مجرد عُرف؛ والأسماء الأطول مثل TItem أو Key تعمل بالطريقة نفسها.

كيف أكتب دالة سهم عامة (generic) في TypeScript؟

ضع قائمة معاملات النوع قبل المعاملات: const first = <T>(items: T[]): T | undefined => items[0];. في ملف .tsx ستُقرأ <T> على أنها وسم JSX، فاكتب <T,> مع فاصلة زائدة، أو <T extends unknown>.

كيف أضبط نوعًا افتراضيًا لـ generic في TypeScript؟

أضف = Type بعد المعامل: interface ApiResponse<T = unknown> { data: T }. عندها يعني ApiResponse دون وسيط ApiResponse<unknown>. ويجب أن تأتي المعاملات ذات القيم الافتراضية بعد المعاملات التي بلا قيم افتراضية، مثل معاملات الدوال الاختيارية.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن