الدليل الشامل للأنواع الحرفية (Literal Types) والتعدادات (Enums) في TypeScript: هندسة الثوابت وضبط الحالات
مقدمة عن الأنواع الحرفية والتعدادات
في رحلتنا المستمرة لاستكشاف أعماق لغة TypeScript، نصل اليوم إلى محطتين بالغتي الأهمية في بناء تطبيقات قوية وموثوقة: الأنواع الحرفية (Literal Types) والتعدادات (Enums). في هندسة البرمجيات، لا يكفي في كثير من الأحيان أن نخبر النظام بأن متغيراً ما هو مجرد “نص” أو “رقم” عام؛ بل نحتاج إلى قصر قيمه المسموحة على مجموعة دقيقة ومعدودة مسبقاً، مثل حالات الطلبات، أو بروتوكولات الاتصال، أو اتجاهات الحركة، أو رموز الاستجابة لخدمات الويب.
تمنحنا الأنواع الحرفية والتعدادات القدرة على تحويل القيم المتوقعة إلى أنواع صريحة يتحقق منها المترجم في وقت الترجمة (Compile Time)، مما يمنع الأخطاء الإملائية الشائعة ويجعل كود التطبيق ذو توثيق ذاتي وصيانة عالية المرونة.
الأنواع الحرفية (Literal Types)
بعد أن استعرضنا في القسم السابق لمحة عامة عن الأنواع الحرفية، حان الوقت لنتعمق في فهمها واستكشاف كيفية استخدامها بفعالية في مشاريع TypeScript. تُعد الأنواع الحرفية أداة قوية تمنحنا القدرة على تحديد أنواع المتغيرات أو المعاملات أو القيم المرجعة بدقة متناهية عبر تخصيص القيمة بحد ذاتها كـ “نوع” مستقل.
في JavaScript التقليدية، يمكن لأي متغير من نوع string أن يحمل أي نص عشوائي في الذاكرة. أما في TypeScript، فعندما تعلن عن نوع حرفي، فإنك تجبر المترجم على رفض أي قيمة لا تتطابق حرفياً مع تلك القيمة المعينة.
أنواع الأعداد الحرفية (Numeric Literal Types)
بعد أن ألقينا الضوء في الأقسام السابقة على المفهوم العام للأنواع الحرفية وكيف تُمكِّننا من تحديد قيم دقيقة للمتغيرات، ننتقل الآن لتعميق فهمنا لأنواع محددة من هذه الأنواع، وهي أنواع الأعداد الحرفية (Numeric Literal Types). هذه الأنواع تُمثل قفزة نوعية في تعزيز دقة وسلامة الشفرة البرمجية، حيث تسمح لنا بتقييد قيم المتغيرات لتكون أرقامًا محددة وثابتة، بدلًا من مجرد أي رقم.
في جوهرها، تُعد أنواع الأعداد الحرفية شكلًا أكثر صرامة من النوع number العام في TypeScript. فبينما يمكن للمتغير من النوع number أن يحمل أي قيمة عددية (سواء كانت صحيحة أو عشرية)، فإن المتغير من النوع العددي الحرفي يلتزم بقيمة رقمية واحدة محددة. هذا التقييد الدقيق يُعزز من قراءة الشفرة ويُقلل من الأخطاء المحتملة بشكل كبير، خاصةً عند التعامل مع الثوابت الرقمية أو القيم المحدودة.
الأعداد الصحيحة الحرفية (Integer Literal Types)
تُعد أنواع الأعداد الصحيحة الحرفية هي الحالة الأكثر شيوعًا ووضوحًا لأنواع الأعداد الحرفية. عند تعريف متغير بنوع عددي حرفي صحيح، فإنك تُخبر TypeScript بأن هذا المتغير لا يمكن أن يحمل سوى تلك القيمة الصحيحة المحددة بالضبط، لا أقل ولا أكثر. لنستعرض بعض الأمثلة لتوضيح هذا المفهوم:
// تعريف نوع عددي حرفي يمثل قيمة ثابتة
type HTTP_STATUS_OK = 200;
let responseStatus: HTTP_STATUS_OK;
// هذا التعيين صحيح لأن القيمة 200 تطابق النوع
responseStatus = 200; // ✅ صحيح
// ❌ خطأ ترجمة فوري عند محاولة إسناد أي رقم آخر:
// responseStatus = 404; // Error: Type '404' is not assignable to type '200'.
// الدمج بين الأعداد الحرفية باستخدام الاتحاد (Union Types)
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
function rollDice(): DiceRoll {
return (Math.floor(Math.random() * 6) + 1) as DiceRoll;
}
const currentRoll = rollDice();
console.log(`قيمة النرد: ${currentRoll}`);أنواع النصوص الحرفية (String Literal Types)
بعد أن استعرضنا في الأقسام السابقة قوة الأنواع الحرفية في تقييد القيم الرقمية، حان الوقت لننتقل إلى عالم النصوص، حيث تبرز أنواع النصوص الحرفية (String Literal Types) كأداة لا غنى عنها في تحديد الخيارات الثابتة ومعالجة الواجهات البرمجية وتكامل مكونات واجهات المستخدم:
// تعريف نوع حرفي لحالات طلب الشبكة
type NetworkState = "idle" | "loading" | "success" | "error";
function handleStateChange(state: NetworkState): void {
switch (state) {
case "idle":
console.log("النظام جاهز وفي وضع الاستعداد.");
break;
case "loading":
console.log("جاري جلب البيانات من الخادم...");
break;
case "success":
console.log("تم تحميل البيانات بنجاح!");
break;
case "error":
console.error("فشلت العملية، يرجى إعادة المحاولة.");
break;
}
}
handleStateChange("loading"); // ✅ صحيح
// handleStateChange("pending"); // ❌ خطأ ترجمة فوري لا يقبل التجاوزأنواع القيم المنطقية (Boolean Literal Types)
في لغة TypeScript، تعتبر القيم المنطقية من الأساسيات التي يجب على كل مطور معرفتها والتعامل معها. القيم المنطقية هي قيم تستخدم للتعبير عن الحالات الثنائية، إما صحيح (true) أو خاطئ (false). تُستخدم القيم المنطقية في العديد من السياقات البرمجية مثل الشروط والتحقق من القيم والعمليات الثنائية.
تعريف أنواع القيم المنطقية الحرفية
النوع العام boolean يقبل القيمة true أو false. لكن يمكنك تقييد الحقل بنوع منطقي حرفي صارم (Boolean Literal) يقبل قيمة واحدة فقط:
type AlwaysTrue = true;
let isFeatureActive: AlwaysTrue = true;
// isFeatureActive = false; // ❌ خطأ: Type 'false' is not assignable to type 'true'.
// الاستخدام الأقوى في الاتحادات المميزة (Discriminated Unions):
interface ValidationSuccess {
isValid: true;
data: string[];
}
interface ValidationFailure {
isValid: false;
errorReason: string;
}
type ValidationResult = ValidationSuccess | ValidationFailure;
function processResult(res: ValidationResult) {
if (res.isValid) {
console.log(`تم التحقق بنجاح! عدد العناصر: ${res.data.length}`);
} else {
console.error(`فشل التحقق: ${res.errorReason}`);
}
}أنواع القيم الفارغة والصفرية (Null Literal Types و Nullable Types)
في هذا القسم، سنتعرف على أنواع القيم الفارغة والصفرية وكيفية استخدامها في تعريف المتغيرات والعمليات المتعلقة بها بأمان في TypeScript.
أنواع القيم الفارغة والصفرية (Null Literal Types)
في TypeScript، تمثل كل من null و undefined أنواعاً حرفية قائمة بذاتها عند تفعيل وضع الفحص الصارم (strictNullChecks: true في tsconfig.json). القيمة null تمثل غياب القيمة المتعمد والمقصود، بينما undefined تمثل متغيراً تم الإعلان عنه دون إسناد قيمة فعلية له في الذاكرة.
أنواع المتغيرات القابلة للقيمة الفارغة (Nullable Types)
في بناء النظم والتطبيقات، غالباً ما نتعامل مع حقول قد تكون متوفرة في لحظة معينة أو غير متوفرة بعد. نقوم بتمثيل هذه المتغيرات باستخدام الاتحاد مع null أو undefined:
const myVar: number | null = null;
const myVar2: number | undefined = undefined;
const myVar3: string | null = 'مرحبا';
function printMessage(param: string | null): void {
if (param !== null) {
// TypeScript يضيق النوع تلقائياً إلى string داخل هذا الفرع
console.log(`تم تمرير قيمة نصية: ${param.toUpperCase()}`);
} else {
console.log('تم تمرير قيمة فارغة (null)');
}
}
printMessage("كود التطور");
printMessage(null);التعامل مع الأنواع المعرفة وغير المعرفة (Known Types vs Unknown)
يقسم نظام TypeScript أنواع البيانات إلى أنواع معروفة ومحددة البنية (Known Types)، وأنواع غير معروفة تتطلب فحصاً حذراً قبل الاستخدام (Unknown Types):
الأنواع المعرفة المدمجة (Known Primitive & Special Types)
- number: يمثل الأعداد الصحيحة والعشرية والأعداد بنظام الفاصلة العائمة (Float/Double).
- bigint: يمثل الأعداد الصحيحة الكبيرة جداً التي تتجاوز حدود
Number.MAX_SAFE_INTEGER. - string: يمثل النصوص الحرفية وسلاسل المحارف.
- boolean: يمثل القيم الثنائية المنطقية (true و false).
- void: يمثل دالة تنفذ إجراءات دون أن تُرجع أي قيمة ذات معنى.
- never: يمثل حالة مستحيلة الحدوث أو دالة ترمي خطأ دائماً وتوقف التنفيذ.
النوع غير المعروف (unknown) مقابل any
النوع unknown هو البديل الآمن نوعياً للنوع any. بينما يسمح لك any باستدعاء أي دالة أو قراءة أي خاصية دون تحقق (مما قد يسبب انهيار وقت التشغيل)، فإن unknown يفرض عليك التحقق الصارم من نوع المتغير قبل استخدامه:
function processDynamicData(data: unknown): void {
// ❌ لا يسمح TypeScript باستدعاء أي خاصية مباشرة:
// console.log(data.length); // Error: 'data' is of type 'unknown'.
// ✅ التحقق الصارم باستخدام حراس الأنواع (Type Narrowing):
if (typeof data === "string") {
console.log(`طول النص: ${data.length}`);
} else if (typeof data === "number") {
console.log(`القيمة المضاعفة: ${data * 2}`);
} else if (data === null || data === undefined) {
console.log("قيمة فارغة أو غير معينة.");
}
}
processDynamicData("منصة كود التطور");
processDynamicData(50);التعدادات (Enums) في TypeScript
التعدادات (Enums) هي ميزة خاصة في TypeScript تسمح للمطورين بتعريف مجموعة من الثوابت الاسمية المترابطة. على عكس الأنواع الحرفية والواجهات التي تُحذف بالكامل عند الترجمة، فإن التعدادات القياسية تظل متواجدة بعد الترجمة ككائنات حقيقية في JavaScript، مما يتيح التكرار عليها والوصول العكسي لقيمها.
1. التعدادات الرقمية (Numeric Enums) والتعيين التلقائي
في التعدادات الرقمية، تبدأ القيم افتراضياً من الصفر وتزداد بمقدار 1 لكل عضو تالٍ، أو يمكن تحديد نقطة بداية يدوية:
enum OrderStatus {
Pending = 1, // بداية الترقيم من 1 بدلاً من 0
Processing, // قيمته تلقائياً 2
Shipped, // قيمته تلقائياً 3
Delivered, // قيمته تلقائياً 4
Cancelled // قيمته تلقائياً 5
}
let currentStatus: OrderStatus = OrderStatus.Processing;
console.log(currentStatus); // يطبع الرقم: 2
// التعيين والوصول العكسي (Reverse Mapping):
let statusName: string = OrderStatus[2];
console.log(statusName); // يطبع الاسم: "Processing"2. التعدادات النصية (String Enums)
في الأنظمة السحابية والاتصال مع قواعد البيانات، تُفضل التعدادات النصية لأنها توفر قيماً واضحة ومقروءة أثناء فحص السجلات (Logs) والرسائل الشبكية، ولا تعتمد على أرقام مبهمة:
enum UserRole {
Admin = "ADMIN",
Editor = "EDITOR",
Subscriber = "SUBSCRIBER"
}
function checkAccess(role: UserRole): boolean {
return role === UserRole.Admin;
}
console.log(checkAccess(UserRole.Admin)); // true
console.log(UserRole.Editor); // "EDITOR"3. التعدادات الثابتة (const Enums) لتحسين الأداء
عندما ترغب في الاستفادة من التعدادات مع تقليل حجم ملفات JavaScript الناتجة وحذف تكلفة الكائنات في الذاكرة، استخدم const enum. يقوم المترجم باستبدال كل استخدام للتعداد بقيمته المباشرة (Inlining) ويحذف تعريف الكائن بالكامل:
const enum HTTPMethod {
GET = "GET",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE"
}
const method = HTTPMethod.GET;
// في ملف JavaScript المترجم سيظهر الكود هكذا مباشرة:
// const method = "GET"; // دون أي كائن إضافي في الذاكرةمقارنة معمارية: التعدادات (Enums) مقابل الأنواع الحرفية (Union of Literals)
أحد أكثر الأسئلة المعمارية شيوعاً بين مطوري TypeScript هو: متى نستخدم enum ومتى نستخدم type Status = 'A' | 'B'؟ يوضح الجدول المقارن التالي المعايير الدقيقة للاختيار:
| وجه المقارنة | التعدادات (Enums) | اتحاد الأنواع الحرفية (Union Literals) |
|---|---|---|
| التواجد في وقت التشغيل | نعم (تتحول لكائن حقيقي في JS ما لم تكن const) | لا (تُحذف بالكامل عند الترجمة) |
| حجم ملف الإخراج المترجم | يضيف كود توليد كائن في JS | صفر بايت إضافي (أصغر حجماً) |
| الوصول العكسي (Reverse Mapping) | مدعوم في التعدادات الرقمية | غير مدعوم |
| إمكانية التكرار في الحلقات | سهل عبر Object.values(MyEnum) | يتطلب تعريف مصفوفة مسبقة مع as const |
| التوافق مع أطر العمل | شائع في الأنظمة المعتمدة على الفئات (NestJS / Angular) | شائع ومفضل في React والمكتبات الوظيفية |
أنواع القيم القابلة للتغيير وثبات البيانات مع as const (Immutability)
في رحلتنا المستمرة لاستكشاف أعماق TypeScript، نصل إلى مفهومين محوريين يؤثران بشكل مباشر على هندسة النظم البرمجية: القيم القابلة للتغيير (Mutable) والقيم غير القابلة للتغيير (Immutable). بشكل افتراضي، يوسع TypeScript نوع المتغيرات المعرفة بـ let أو داخل الكائنات إلى النوع العام (Type Widening). على سبيل المثال:
let action = "OPEN"; // يستنتجه TypeScript كـ string عام وليس النوع الحرفي "OPEN"
const fixedAction = "OPEN"; // يستنتجه كنوع حرفي "OPEN" لأن const يمنع إعادة التعيين
// مع الكائنات:
const config = {
endpoint: "https://api.code-elta6ur.com",
maxRetries: 3
};
// نوع config.endpoint هنا هو string عادي وليس الرابط الحرفيلتحويل الكائنات والمصفوفات بالكامل إلى كائنات ثابتة غير قابلة للتعديل مع الحفاظ على أنواعها الحرفية الدقيقة، نستخدم تأكيد الثبات const assertions (as const):
const AppTheme = {
primaryColor: "#0070f3",
secondaryColor: "#ff4081",
borderRadius: 8
} as const;
// النتيجة:
// جميع الحقول أصبحت readonly وحملت قيمها الحرفية الدقيقة بدلاً من string أو number العام
// AppTheme.primaryColor = "#000"; // ❌ خطأ ترجمة فوري: Cannot assign to 'primaryColor' because it is a read-only property.
// اشتقاق نوع اتحادي تلقائياً من الكائن:
type ThemeColor = typeof AppTheme[keyof typeof AppTheme]; // "#0070f3" | "#ff4081" | 8أفضل الممارسات وحل المشكلات الشائعة
لضمان كتابة شيفرات برمجية خالية من الثغرات وسهلة الصيانة عند استخدام الأنواع الحرفية والتعدادات، يُنصح بالالتزام بالإرشادات الهندسية التالية:
- تجنب استخدام التعدادات الهجينة (Heterogeneous Enums): لا تخلط بين الأرقام والنصوص في نفس التعداد (مثل
enum Bad { A = 1, B = "B" }) لأن ذلك يربك المترجم ويصعب فحص الحالات. - استخدم التعدادات النصية كمعيار مفضل للتعدادات: تجنب التعدادات الرقمية الافتراضية إذا كانت القيم ستُرسل عبر الشبكة أو تُخزن في قواعد البيانات، لتفادي أخطاء تغير ترتيب الفهارس عند التحديث.
- تفضيل كائنات
as constكبديل حديث للتعدادات: في تطبيقات الواجهات الأمامية الحديثة، يوفر نمطconst Config = { ... } as constنفس فوائد التعدادات دون توليد أي كود زائد في ملفات JavaScript الناتجة. - تطبيق فحص الشمولية (Exhaustiveness Checking): عند فحص حالات التعداد أو الأنواع الحرفية داخل جمل
switch، استخدم فرعdefaultمع النوعneverلاكتشاف أي حالة غير مغطاة عند إضافة خيارات جديدة مستقبلاً.
الخلاصة والخطوات القادمة
في هذا الدرس التدريبي الشامل من دورة TypeScript من الصفر إلى الاحتراف (الدرس #16)، تعرفنا على الأنواع الحرفية (Literal Types) والتعدادات (Enums) بكافة أبعادها الهندسية. استكشفنا كيف تمكننا الأعداد والنصوص الحرفية من صياغة عقود برمجية محكمة، وكيف توفر التعدادات الرقمية والنصية و const enums واجهات وصفية لبيانات الأنظمة، وصولاً إلى تعزيز ثبات البيانات باستخدام as const.
في الدروس القادمة، سنبني فوق هذه المفاهيم لنتعرف على الأنواع المخططة (Mapped Types) والأنواع الشرطية المتقدمة (Conditional Types) لنتعلم كيفية اشتقاق أنواع برمجية ديناميكية تتكيف تلقائياً مع بنيات مشاريعك المعقدة.
المراجع والمصادر الرسمية المعتمدة
- التوثيق الرسمي لـ TypeScript: الأنواع الحرفية (Literal Types)
- التوثيق الرسمي لـ TypeScript: التعدادات (Enums) وقواعد استخدامها
- دورة TypeScript من الصفر إلى الاحتراف – منصة كود التطور
اكتشاف المزيد من كود التطور
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.
