أدوات TypeScript Generics للاستخدام اليومي
أنت تكتب دالة تُرجع العنصر الأول من مصفوفة. تريدها أن تعمل مع الأرقام والسلاسل والكائنات، ولكنك تريد أيضًا أن يتطابق نوع الإرجاع مع نوع الإدخال. بدون generics، ستلجأ إلى any وتفقد كل أمان الأنواع. هنا يأتي دور TypeScript generics.
تتيح لك Generics كتابة كود يعمل مع أنواع متعددة مع الحفاظ على معلومات النوع المحددة. في هذا الدليل، سنستكشف أنماطًا عملية لاستخدام generics في كود TypeScript اليومي.
ما هي Generics؟
Generics هي طريقة لتحديد معاملات للأنواع. تمامًا كما تأخذ الدوال معاملات، تأخذ generics معاملات نوع. تسمح لك بإنشاء مكونات قابلة لإعادة الاستخدام تعمل مع مجموعة متنوعة من الأنواع بدلاً من نوع واحد.
خذ بعين الاعتبار دالة الهوية هذه:
function identity<T>(arg: T): T {
return arg;
}
هنا، T هو متغير نوع. عند استدعاء identity(42)، يستنتج TypeScript أن T هو number. عند استدعاء identity("hello")، يصبح T هو string. نوع الإرجاع يتطابق دائمًا مع نوع الإدخال.
الدوال العامة في الممارسة
لنلقِ نظرة على سيناريو شائع: دالة تغلف قيمة في كائن.
function wrapInObject<T>(value: T): { value: T } {
return { value };
}
بدون generics، سيكون عليك استخدام any أو unknown، وسيفقد المستدعون معلومات النوع. مع generics، نوع الإرجاع هو بدقة { value: T }.
مثال يومي آخر هو دالة تُرجع العنصر الأخير من مصفوفة:
function last<T>(arr: T[]): T | undefined {
return arr[arr.length - 1];
}
يستنتج TypeScript T من وسيطة المصفوفة، لذا last([1, 2, 3]) يُرجع number | undefined.
قيود Generics
أحيانًا تريد تقييد الأنواع التي يمكن استخدامها مع generic الخاص بك. على سبيل المثال، قد تحتاج إلى الوصول إلى خاصية موجودة فقط في أنواع معينة. يمكنك استخدام الكلمة المفتاحية extends لإضافة قيد.
function getLength<T extends { length: number }>(item: T): number {
return item.length;
}
الآن يجب أن يمتلك T خاصية length من النوع number. هذا يعمل مع السلاسل والمصفوفات وأي كائن له خاصية length.
يمكنك أيضًا استخدام القيود مع الواجهات:
interface HasId {
id: string;
}
function findById<T extends HasId>(items: T[], id: string): T | undefined {
return items.find(item => item.id === id);
}
يضمن هذا أن العناصر الممررة إلى findById لديها خاصية id.
الواجهات العامة والأسماء المستعارة للنوع
لا تقتصر generics على الدوال. يمكنك تعريف واجهات عامة وأسماء مستعارة للنوع لإنشاء هياكل بيانات مرنة.
interface ApiResponse<T> {
data: T;
status: number;
error?: string;
}
async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
const data = await response.json();
return { data, status: response.status };
}
الآن يمكنك كتابة استجابات API بدقة:
interface User {
id: number;
name: string;
}
const userResponse = await fetchData<User>('/api/user');
// userResponse.data is User
الفئات العامة
يمكن أن تكون الفئات عامة أيضًا. مثال شائع هو مكدس بسيط:
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];
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
const num = numberStack.pop(); // number | undefined
Generics في مكونات React
إذا كنت تبني مكونات React، يمكن أن تجعل generics مكوناتك أكثر قابلية لإعادة الاستخدام. على سبيل المثال، مكون قائمة يعرض عناصر من أي نوع:
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return <ul>{items.map((item, index) => <li key={index}>{renderItem(item)}</li>)}</ul>;
}
الآن يمكنك استخدام List مع أي نوع والحصول على فحص كامل للنوع على دالة renderItem.
المزالق الشائعة وأفضل الممارسات
- لا تفرط في استخدام generics. إذا كانت دالة تعمل فقط مع نوع معين، فاستخدم هذا النوع فقط. Generics تضيف تعقيدًا.
- استخدم أسماء معاملات نوع وصفية. بينما
Tشائع، لعدة معاملات استخدم أسماء مثلTInput،TOutput. - فضل القيود على
any. القيود تمنحك أمان الأنواع مع بقائها مرنة. - تذكر أن generics تُمحى في وقت التشغيل. إنها موجودة فقط في وقت الترجمة.
مقارنة: Generics مقابل any مقابل أنواع الاتحاد
| النهج | أمان الأنواع | المرونة | حالة الاستخدام |
|---|---|---|---|
any |
لا يوجد | عالي | ترحيل JS، نماذج أولية سريعة |
| أنواع الاتحاد | جيد | محدود بالأنواع المعروفة | عندما تعرف جميع الأنواع الممكنة |
| Generics | قوي | عالي | مكونات قابلة لإعادة الاستخدام مع أنواع محفوظة |
الأسئلة الشائعة
متى يجب استخدام generics بدلاً من any؟
استخدم generics عندما تريد الحفاظ على معلومات النوع عبر دالة أو مكون. any تعطل فحص النوع، بينما generics تحافظ عليه. إذا وجدت نفسك تتجه إلى any لجعل شيء ما يعمل، فكر فيما إذا كان generic يمكن أن يحلها بشكل أكثر أمانًا.
هل يمكنني استخدام معاملات نوع متعددة؟
نعم. يمكنك تعريف دوال بمعاملات نوع متعددة، مثل function pair<K, V>(key: K, value: V): [K, V]. استخدم أسماء وصفية عندما يكون هناك أكثر من واحد.
كيف تعمل generics مع دوال الأسهم في ملفات .tsx؟
في ملفات .tsx، يمكن أن يكون <T> غامضًا مع JSX. استخدم فاصلة زائدة: const identity = <T,>(arg: T): T => arg; أو استخدم extends unknown: <T extends unknown>.
Generics هي ميزة قوية يمكن أن تجعل كود TypeScript الخاص بك أكثر قابلية لإعادة الاستخدام وأمانًا من حيث الأنواع. ابدأ بدوال generic بسيطة، ثم طبقها تدريجيًا على الواجهات والفئات. إذا كنت بحاجة إلى تنسيق أو التحقق من بيانات JSON بأمان الأنواع، تحقق من أداة JSON Formatter الخاصة بنا لفحص وطباعة JSON الخاص بك بسرعة.