تعلم كيفية الاستفادة من أنواع TypeScript المخططة لتحويل هياكل الكائنات ديناميكيًا، مما يتيح كتابة كود قوي وقابل للصيانة للتطبيقات العالمية.
أنواع TypeScript المخططة لتحويلات الكائنات الديناميكية: دليل شامل
تُمكّن TypeScript، بتركيزها القوي على الكتابة الثابتة، المطورين من كتابة كود أكثر موثوقية وقابلية للصيانة. ومن الميزات الحاسمة التي تساهم بشكل كبير في ذلك هي الأنواع المخططة (mapped types). يتعمق هذا الدليل في عالم أنواع TypeScript المخططة، ويقدم فهمًا شاملاً لوظائفها وفوائدها وتطبيقاتها العملية، خاصة في سياق تطوير حلول البرمجيات العالمية.
فهم المفاهيم الأساسية
في جوهره، يتيح لك النوع المخطط إنشاء نوع جديد بناءً على خصائص نوع موجود. يمكنك تعريف نوع جديد عن طريق التكرار على مفاتيح نوع آخر وتطبيق تحويلات على القيم. وهذا مفيد بشكل لا يصدق في السيناريوهات التي تحتاج فيها إلى تعديل بنية الكائنات ديناميكيًا، مثل تغيير أنواع بيانات الخصائص، أو جعل الخصائص اختيارية، أو إضافة خصائص جديدة بناءً على الخصائص الموجودة.
لنبدأ بالأساسيات. لنأخذ واجهة بسيطة كمثال:
interface Person {
name: string;
age: number;
email: string;
}
الآن، لنعرّف نوعًا مخططًا يجعل جميع خصائص Person
اختيارية:
type OptionalPerson = {
[K in keyof Person]?: Person[K];
};
في هذا المثال:
[K in keyof Person]
يتكرر عبر كل مفتاح (name
,age
,email
) من واجهةPerson
.?
يجعل كل خاصية اختيارية.Person[K]
يشير إلى نوع الخاصية في واجهةPerson
الأصلية.
النوع الناتج OptionalPerson
يبدو فعليًا هكذا:
{
name?: string;
age?: number;
email?: string;
}
هذا يوضح قوة الأنواع المخططة في تعديل الأنواع الموجودة ديناميكيًا.
صيغة وبنية الأنواع المخططة
صيغة النوع المخطط محددة تمامًا وتتبع هذه البنية العامة:
type NewType = {
[Key in KeysType]: ValueType;
};
لنفصّل كل مكون:
NewType
: الاسم الذي تخصصه للنوع الجديد الذي يتم إنشاؤه.[Key in KeysType]
: هذا هو جوهر النوع المخطط.Key
هو المتغير الذي يتكرر عبر كل عضو فيKeysType
. غالبًا ما تكونKeysType
هيkeyof
لنوع آخر (كما في مثالOptionalPerson
)، ولكن ليس دائمًا. يمكن أن تكون أيضًا اتحادًا من السلاسل النصية الحرفية أو نوعًا أكثر تعقيدًا.ValueType
: يحدد نوع الخاصية في النوع الجديد. يمكن أن يكون نوعًا مباشرًا (مثلstring
)، أو نوعًا يعتمد على خاصية النوع الأصلي (مثلPerson[K]
)، أو تحويلاً أكثر تعقيدًا للنوع الأصلي.
مثال: تحويل أنواع الخصائص
تخيل أنك تحتاج إلى تحويل جميع الخصائص الرقمية في كائن ما إلى سلاسل نصية. إليك كيف يمكنك القيام بذلك باستخدام نوع مخطط:
interface Product {
id: number;
name: string;
price: number;
quantity: number;
}
type StringifiedProduct = {
[K in keyof Product]: Product[K] extends number ? string : Product[K];
};
في هذه الحالة، نحن نقوم بـ:
- التكرار عبر كل مفتاح في واجهة
Product
. - استخدام نوع شرطي (
Product[K] extends number ? string : Product[K]
) للتحقق مما إذا كانت الخاصية رقمًا. - إذا كانت رقمًا، فإننا نضبط نوع الخاصية على
string
؛ وإلا، فإننا نحتفظ بالنوع الأصلي.
النوع الناتج StringifiedProduct
سيكون:
{
id: string;
name: string;
price: string;
quantity: string;
}
الميزات والتقنيات الرئيسية
1. استخدام keyof
وتوقيعات الفهرس
كما تم توضيحه سابقًا، يعد keyof
أداة أساسية للعمل مع الأنواع المخططة. يمكّنك من التكرار على مفاتيح النوع. توفر توقيعات الفهرس طريقة لتعريف نوع الخصائص عندما لا تعرف المفاتيح مسبقًا، ولكنك لا تزال ترغب في تحويلها.
مثال: تحويل جميع الخصائص بناءً على توقيع الفهرس
interface StringMap {
[key: string]: number;
}
type StringMapToString = {
[K in keyof StringMap]: string;
};
هنا، يتم تحويل جميع القيم الرقمية في StringMap إلى سلاسل نصية داخل النوع الجديد.
2. الأنواع الشرطية داخل الأنواع المخططة
الأنواع الشرطية هي ميزة قوية في TypeScript تتيح لك التعبير عن علاقات الأنواع بناءً على الشروط. عند دمجها مع الأنواع المخططة، فإنها تسمح بتحويلات معقدة للغاية.
مثال: إزالة Null و Undefined من نوع ما
type NonNullableProperties = {
[K in keyof T]: T[K] extends (null | undefined) ? never : T[K];
};
هذا النوع المخطط يتكرر عبر جميع مفاتيح النوع T
ويستخدم نوعًا شرطيًا للتحقق مما إذا كانت القيمة تسمح بـ null أو undefined. إذا كان الأمر كذلك، فسيتم تقييم النوع إلى never، مما يؤدي فعليًا إلى إزالة تلك الخاصية؛ وإلا، فإنه يحتفظ بالنوع الأصلي. هذا النهج يجعل الأنواع أكثر قوة عن طريق استبعاد قيم null أو undefined التي قد تكون إشكالية، مما يحسن جودة الكود ويتماشى مع أفضل الممارسات لتطوير البرمجيات العالمية.
3. الأنواع المساعدة (Utility Types) للكفاءة
توفر TypeScript أنواعًا مساعدة مدمجة تبسط مهام معالجة الأنواع الشائعة. تستفيد هذه الأنواع من الأنواع المخططة خلف الكواليس.
Partial
: يجعل جميع خصائص النوعT
اختيارية (كما هو موضح في مثال سابق).Required
: يجعل جميع خصائص النوعT
مطلوبة.Readonly
: يجعل جميع خصائص النوعT
للقراءة فقط.Pick
: ينشئ نوعًا جديدًا يحتوي فقط على المفاتيح المحددة (K
) من النوعT
.Omit
: ينشئ نوعًا جديدًا يحتوي على جميع خصائص النوعT
باستثناء المفاتيح المحددة (K
).
مثال: استخدام Pick
و Omit
interface User {
id: number;
name: string;
email: string;
role: string;
}
type UserSummary = Pick;
// { id: number; name: string; }
type UserWithoutEmail = Omit;
// { id: number; name: string; role: string; }
توفر عليك هذه الأنواع المساعدة كتابة تعريفات أنواع مخططة متكررة وتحسن من قابلية قراءة الكود. وهي مفيدة بشكل خاص في التطوير العالمي لإدارة طرق عرض مختلفة أو مستويات مختلفة من الوصول إلى البيانات بناءً على أذونات المستخدم أو سياق التطبيق.
التطبيقات والأمثلة في العالم الحقيقي
1. التحقق من صحة البيانات وتحويلها
تعتبر الأنواع المخططة لا تقدر بثمن للتحقق من صحة البيانات الواردة من مصادر خارجية (واجهات برمجة التطبيقات، قواعد البيانات، إدخالات المستخدم) وتحويلها. وهذا أمر بالغ الأهمية في التطبيقات العالمية حيث قد تتعامل مع بيانات من مصادر مختلفة كثيرة وتحتاج إلى ضمان سلامة البيانات. تمكّنك من تحديد قواعد محددة، مثل التحقق من أنواع البيانات، وتعديل هياكل البيانات تلقائيًا بناءً على هذه القواعد.
مثال: تحويل استجابة واجهة برمجة التطبيقات (API)
interface ApiResponse {
userId: string;
id: string;
title: string;
completed: boolean;
}
type CleanedApiResponse = {
[K in keyof ApiResponse]:
K extends 'userId' | 'id' ? number :
K extends 'title' ? string :
K extends 'completed' ? boolean : any;
};
هذا المثال يحول خصائص userId
و id
(التي كانت في الأصل سلاسل نصية من واجهة برمجة التطبيقات) إلى أرقام. يتم تحديد نوع خاصية title
بشكل صحيح كسلسلة نصية، ويتم الاحتفاظ بـ completed
كقيمة منطقية. هذا يضمن اتساق البيانات ويتجنب الأخطاء المحتملة في المعالجة اللاحقة.
2. إنشاء دعائم (Props) مكونات قابلة لإعادة الاستخدام
في React وأطر عمل واجهة المستخدم الأخرى، يمكن للأنواع المخططة تبسيط إنشاء دعائم مكونات قابلة لإعادة الاستخدام. وهذا مهم بشكل خاص عند تطوير مكونات واجهة مستخدم عالمية يجب أن تتكيف مع مختلف اللغات المحلية وواجهات المستخدم.
مثال: التعامل مع الترجمة (Localization)
interface TextProps {
textId: string;
defaultText: string;
locale: string;
}
type LocalizedTextProps = {
[K in keyof TextProps as `localized-${K}`]: TextProps[K];
};
في هذا الكود، يضيف النوع الجديد LocalizedTextProps
بادئة إلى اسم كل خاصية من خصائص TextProps
. على سبيل المثال، تصبح textId
localized-textId
، وهو أمر مفيد لتعيين دعائم المكونات. يمكن استخدام هذا النمط لإنشاء دعائم تسمح بتغيير النص ديناميكيًا بناءً على لغة المستخدم المحلية. وهذا ضروري لبناء واجهات مستخدم متعددة اللغات تعمل بسلاسة عبر مناطق ولغات مختلفة، كما هو الحال في تطبيقات التجارة الإلكترونية أو منصات التواصل الاجتماعي الدولية. توفر الدعائم المحولة للمطور مزيدًا من التحكم في الترجمة والقدرة على إنشاء تجربة مستخدم متسقة في جميع أنحاء العالم.
3. إنشاء النماذج الديناميكية
تعد الأنواع المخططة مفيدة لإنشاء حقول النماذج ديناميكيًا بناءً على نماذج البيانات. في التطبيقات العالمية، يمكن أن يكون هذا مفيدًا لإنشاء نماذج تتكيف مع أدوار المستخدمين المختلفة أو متطلبات البيانات.
مثال: إنشاء حقول النموذج تلقائيًا بناءً على مفاتيح الكائن
interface UserProfile {
firstName: string;
lastName: string;
email: string;
phoneNumber: string;
}
type FormFields = {
[K in keyof UserProfile]: {
label: string;
type: string;
required: boolean;
};
};
هذا يسمح لك بتحديد بنية النموذج بناءً على خصائص واجهة UserProfile
. هذا يتجنب الحاجة إلى تحديد حقول النموذج يدويًا، مما يحسن من مرونة وقابلية صيانة تطبيقك.
تقنيات الأنواع المخططة المتقدمة
1. إعادة تخطيط المفاتيح (Key Remapping)
قدمت TypeScript 4.1 إعادة تخطيط المفاتيح في الأنواع المخططة. يسمح لك هذا بإعادة تسمية المفاتيح أثناء تحويل النوع. هذا مفيد بشكل خاص عند تكييف الأنواع مع متطلبات واجهة برمجة التطبيقات المختلفة أو عندما تريد إنشاء أسماء خصائص أكثر سهولة في الاستخدام.
مثال: إعادة تسمية الخصائص
interface Product {
productId: number;
productName: string;
productDescription: string;
price: number;
}
type ProductDto = {
[K in keyof Product as `dto_${K}`]: Product[K];
};
هذا يعيد تسمية كل خاصية من نوع Product
لتبدأ بـ dto_
. وهذا أمر قيم عند التخطيط بين نماذج البيانات وواجهات برمجة التطبيقات التي تستخدم اصطلاحات تسمية مختلفة. إنه مهم في تطوير البرمجيات الدولية حيث تتفاعل التطبيقات مع أنظمة خلفية متعددة قد يكون لها اصطلاحات تسمية محددة، مما يسمح بالتكامل السلس.
2. إعادة تخطيط المفاتيح الشرطية
يمكنك دمج إعادة تخطيط المفاتيح مع الأنواع الشرطية لإجراء تحويلات أكثر تعقيدًا، مما يسمح لك بإعادة تسمية أو استبعاد الخصائص بناءً على معايير معينة. تتيح هذه التقنية إجراء تحويلات متطورة.
مثال: استبعاد الخصائص من كائن نقل البيانات (DTO)
interface Product {
id: number;
name: string;
description: string;
price: number;
category: string;
isActive: boolean;
}
type ProductDto = {
[K in keyof Product as K extends 'description' | 'isActive' ? never : K]: Product[K]
}
هنا، تتم إزالة خصائص description
و isActive
بشكل فعال من نوع ProductDto
الناتج لأن المفتاح يتم تقييمه إلى never
إذا كانت الخاصية 'description' أو 'isActive'. يسمح هذا بإنشاء كائنات نقل بيانات محددة (DTOs) تحتوي فقط على البيانات اللازمة لعمليات مختلفة. يعد نقل البيانات الانتقائي هذا أمرًا حيويًا للتحسين والخصوصية في تطبيق عالمي. تضمن قيود نقل البيانات إرسال البيانات ذات الصلة فقط عبر الشبكات، مما يقلل من استخدام النطاق الترددي ويحسن تجربة المستخدم. وهذا يتماشى مع لوائح الخصوصية العالمية.
3. استخدام الأنواع المخططة مع الأنواع العامة (Generics)
يمكن دمج الأنواع المخططة مع الأنواع العامة لإنشاء تعريفات أنواع مرنة للغاية وقابلة لإعادة الاستخدام. يمكّنك هذا من كتابة كود يمكنه التعامل مع مجموعة متنوعة من الأنواع المختلفة، مما يزيد بشكل كبير من قابلية إعادة استخدام وصيانة الكود الخاص بك، وهو أمر ذو قيمة خاصة في المشاريع الكبيرة والفرق الدولية.
مثال: دالة عامة لتحويل خصائص الكائن
function transformObjectValues(obj: T, transform: (value: T[K]) => U): {
[P in keyof T]: U;
} {
const result: any = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = transform(obj[key]);
}
}
return result;
}
interface Order {
id: number;
items: string[];
total: number;
}
const order: Order = {
id: 123,
items: ['apple', 'banana'],
total: 5.99,
};
const stringifiedOrder = transformObjectValues(order, (value) => String(value));
// stringifiedOrder: { id: string; items: string; total: string; }
في هذا المثال، تستخدم الدالة transformObjectValues
الأنواع العامة (T
, K
, and U
) لأخذ كائن (obj
) من النوع T
، ودالة تحويل تقبل خاصية واحدة من T وتعيد قيمة من النوع U. ثم تعيد الدالة كائنًا جديدًا يحتوي على نفس المفاتيح مثل الكائن الأصلي ولكن بقيم تم تحويلها إلى النوع U.
أفضل الممارسات والاعتبارات
1. أمان الأنواع وقابلية صيانة الكود
واحدة من أكبر فوائد TypeScript والأنواع المخططة هي زيادة أمان الأنواع. من خلال تحديد أنواع واضحة، يمكنك اكتشاف الأخطاء في وقت مبكر أثناء التطوير، مما يقلل من احتمالية حدوث أخطاء في وقت التشغيل. إنها تجعل الكود أسهل في الفهم وإعادة الهيكلة، خاصة في المشاريع الكبيرة. علاوة على ذلك، يضمن استخدام الأنواع المخططة أن يكون الكود أقل عرضة للأخطاء مع توسع البرنامج، والتكيف مع احتياجات ملايين المستخدمين على مستوى العالم.
2. القابلية للقراءة وأسلوب الكود
بينما يمكن أن تكون الأنواع المخططة قوية، من الضروري كتابتها بطريقة واضحة وقابلة للقراءة. استخدم أسماء متغيرات ذات معنى وعلق على الكود لشرح الغرض من التحويلات المعقدة. يضمن وضوح الكود أن يتمكن المطورون من جميع الخلفيات من قراءة وفهم الكود. يساهم الاتساق في الأسلوب واصطلاحات التسمية والتنسيق في جعل الكود أكثر سهولة ويساهم في عملية تطوير أكثر سلاسة، خاصة في الفرق الدولية حيث يعمل أعضاء مختلفون على أجزاء مختلفة من البرنامج.
3. الإفراط في الاستخدام والتعقيد
تجنب الإفراط في استخدام الأنواع المخططة. على الرغم من أنها قوية، إلا أنها يمكن أن تجعل الكود أقل قابلية للقراءة إذا تم استخدامها بشكل مفرط أو عندما تكون هناك حلول أبسط متاحة. فكر فيما إذا كان تعريف واجهة مباشر أو دالة مساعدة بسيطة قد يكون حلاً أكثر ملاءمة. إذا أصبحت أنواعك معقدة للغاية، فقد يكون من الصعب فهمها وصيانتها. ضع في اعتبارك دائمًا التوازن بين أمان الأنواع وقابلية قراءة الكود. يضمن تحقيق هذا التوازن أن يتمكن جميع أعضاء الفريق الدولي من قراءة الكود وفهمه وصيانته بفعالية.
4. الأداء
تؤثر الأنواع المخططة بشكل أساسي على التحقق من الأنواع في وقت الترجمة وعادة لا تقدم عبء أداء كبير في وقت التشغيل. ومع ذلك، قد تؤدي معالجات الأنواع المعقدة للغاية إلى إبطاء عملية الترجمة. قلل من التعقيد وفكر في التأثير على أوقات البناء، خاصة في المشاريع الكبيرة أو للفرق المنتشرة عبر مناطق زمنية مختلفة وبقيود موارد متفاوتة.
الخاتمة
تقدم أنواع TypeScript المخططة مجموعة قوية من الأدوات لتحويل هياكل الكائنات ديناميكيًا. إنها لا تقدر بثمن لبناء كود آمن من حيث النوع وقابل للصيانة وقابل لإعادة الاستخدام، خاصة عند التعامل مع نماذج البيانات المعقدة وتفاعلات واجهة برمجة التطبيقات وتطوير مكونات واجهة المستخدم. من خلال إتقان الأنواع المخططة، يمكنك كتابة تطبيقات أكثر قوة وقابلية للتكيف، وإنشاء برامج أفضل للسوق العالمية. بالنسبة للفرق الدولية والمشاريع العالمية، يوفر استخدام الأنواع المخططة جودة كود قوية وقابلية للصيانة. الميزات التي تمت مناقشتها هنا حاسمة لبناء برامج قابلة للتكيف والتوسع، وتحسين قابلية صيانة الكود، وإنشاء تجارب أفضل للمستخدمين في جميع أنحاء العالم. تجعل الأنواع المخططة تحديث الكود أسهل عند إضافة ميزات جديدة أو واجهات برمجة تطبيقات أو نماذج بيانات أو تعديلها.