النوع الحرفي نوع له قيمة واحدة بالضبط: "up" نوع عضوه الوحيد هو النص "up"، و404 نوع عضوه الوحيد هو الرقم 404. وحدها ليست مفيدة كثيرًا. لكن عند جمعها في union تعطيك متغيرًا يقبل مجموعة ثابتة من القيم ولا شيء غيرها.
دون تعليق @ts-expect-error يكون الاستدعاء الأخير خطأ وقت الترجمة (TS2345). ومعه يُترجم البرنامج ويعمل الاستدعاء ويطبع moving north by 1: الأنواع الحرفية موجودة للمترجم فقط، ووقت التشغيل تكون القيمة نصًا عاديًا.
القيم الحرفية النصية والرقمية والمنطقية
يمكن كتابة أي قيمة نصية أو رقمية أو bigint أو منطقية كنوع. عندها لا يقبل المترجم إلا تلك القيمة بالضبط.
let method: "GET" = "GET";
let code: 404 = 404;
let ok: true = true;
let big: 10n = 10n;
type Port = 80 | 443 | 8080;
const port: Port = 443;
boolean نفسه مجرد union هو true | false، ولهذا يترك تضييق boolean بـ if (flag) القيمة false في فرع else.
| النوع الحرفي | يسمح بـ | النوع الأوسع |
|---|---|---|
"GET" | النص "GET" فقط | string |
404 | الرقم 404 فقط | number |
10n | الـ bigint 10 فقط | bigint |
true | true فقط | boolean |
Union من القيم الحرفية
الاستخدام الشائع هو union يسرد كل القيم المسموحة. داخل الدالة يضيّق المترجم الـ union كلما فحصته، فيعرف كل فرع القيمة التي لديه بالضبط.
الـ union من النصوص الحرفية هو البديل المعتاد في TypeScript عن enum. لا يكلف شيئًا وقت التشغيل، والقيم نصوص عادية يمكنك تسجيلها وإرسالها عبر JSON، والخطأ الإملائي خطأ ترجمة. المزيد عن الموازنة بينهما في enums.
التوسيع: let مقابل const
عندما تستنتج TypeScript نوعًا من قيمة حرفية، تنظر في ما إذا كانت القيمة قابلة للتغيير. لا يمكن أبدًا إعادة إسناد متغير const، لذلك يحتفظ بالنوع الحرفي. أما متغير let فيمكن، لذلك يُوسَّع نوعه إلى النوع العام.
مرّر المؤشر فوق كل اسم في المحرر لترى النوع المستنتج. إذا أردت let لا يحمل إلا قيمًا محددة، فحدد نوعه: let mode: "light" | "dark" = "light".
لماذا تتوسع خصائص الكائنات
خصائص الكائن الحرفي قابلة للتغيير، لذلك تتوسع هي أيضًا، حتى عندما يُخزَّن الكائن في const. هذه أكثر طريقة شائعة لمصادفة الأنواع الحرفية دون قصد:
يبلّغ المترجم:
index.ts(7,15): error TS2345: Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
يُستنتج req على أنه { url: string; method: string } لأن كودًا لاحقًا قد يشغّل req.method = "DELETE". هناك ثلاثة حلول:
وخيار رابع هو satisfies، الذي يفحص الكائن مقابل نوع مع الحفاظ على الأنواع الحرفية لخصائصه.
as const
as const تأكيد const. ضعه بعد تعبير فيستنتج المترجم أضيق نوع يستطيعه:
- تحتفظ القيم النصية والرقمية والمنطقية بأنواعها الحرفية
- تصبح خصائص الكائن
readonly - تصبح المصفوفات الحرفية tuples
readonlyبطول ثابت
التأكيد وقت الترجمة فقط. كود JavaScript الناتج هو الكائن الحرفي نفسه بعد حذف as const، فلا شيء يمنع كودًا آخر من تعديله وقت التشغيل. إذا احتجت إلى ضمان وقت التشغيل، فاستدعِ Object.freeze أيضًا.
Union type من مصفوفة as const
نمط شائع هو الاحتفاظ بالقيم المسموحة في مصفوفة واحدة يمكنك التكرار عليها وقت التشغيل، واشتقاق الـ union منها. يعني (typeof arr)[number] «نوع أي عنصر من arr».
دون as const يكون ROLES من نوع string[] ويكون Role مجرد string. التحويل إلى readonly string[] في isRole لازم لأن includes على tuple من القيم الحرفية لا يقبل إلا تلك القيم، والغرض من الدالة هو اختبار نص قد لا يكون منها. والنمط نفسه مع الكائنات يعمل لخرائط المفاتيح والقيم: const Status = { Active: "active", Banned: "banned" } as const، ثم type Status = (typeof Status)[keyof typeof Status].
معاملات النوع const
الدالة العامة (generic) توسّع عادة القيم الحرفية التي تمررها إليها. منذ TypeScript 5.0 يمكنك تعليم معامل نوع بـ const، فيستنتج المترجم الوسيط كما لو كان عليه as const، دون أن يُطلب من المستدعي كتابته.
هذه في الغالب أداة لمؤلفي المكتبات: تستخدمها تعريفات المسارات وأدوات البناء ومساعدات المخططات حتى يحصل المستدعون على أنواع دقيقة من قيم حرفية عادية.
معاني const
تظهر الكلمة const في أربعة أماكن مختلفة في كود TypeScript:
| الصياغة | النوع | ما تفعله |
|---|---|---|
const x = 1 | تصريح JavaScript | لا يمكن إعادة إسناد الربط؛ والقيمة الحرفية تحتفظ بنوعها الحرفي |
expr as const | تأكيد TypeScript | أضيق نوع: قيم حرفية، وخصائص readonly، وtuples للقراءة فقط |
function f<const T>() | معامل نوع في TypeScript | يستنتج الوسائط كما لو كان عليها as const |
const enum E {} | enum في TypeScript | enum تُضمَّن أعضاؤه وقت الترجمة |
لا يجمد أي منها كائنًا وقت التشغيل. const obj = { a: 1 } ما زال يسمح بـ obj.a = 2؛ الخطأ فقط هو إعادة إسناد obj نفسه.
الأسئلة الشائعة
ما هو النوع الحرفي في TypeScript؟
نوع يسمح بقيمة واحدة بالضبط. "GET" نوع قيمته الوحيدة هي النص "GET"، و404 نوع قيمته الوحيدة هي الرقم 404، وtrue نوع قيمته الوحيدة هي true. وتكون أكثر فائدة عند جمعها في union، مثل type Method = "GET" | "POST".
ماذا يفعل as const في TypeScript؟
as const تأكيد const (const assertion). يخبر المترجم باستنتاج أضيق نوع للتعبير: تحتفظ القيم النصية والرقمية بأنواعها الحرفية، وتصبح خصائص الكائن readonly، وتصبح المصفوفات الحرفية tuples للقراءة فقط. يغيّر النوع فقط؛ فالقيمة وقت التشغيل هي الكائن أو المصفوفة العادية نفسها، وليست مجمدة.
لماذا تستنتج TypeScript النوع string بدل القيمة الحرفية التي كتبتها؟
لأن القيمة قابلة للتغيير. يمكن إعادة إسناد let x = "a" والخاصية في { method: "GET" } لاحقًا، لذلك توسّعهما TypeScript إلى string. أما متغير const فيحتفظ بالنوع الحرفي "a". للحفاظ على القيم الحرفية داخل كائن، حدد نوعه بالنوع الحرفي، أو استخدم as const، أو استخدم satisfies.
ما الفرق بين const و as const؟
const تصريح في JavaScript: لا يمكن إعادة إسناد المتغير، لكن الكائن الذي يشير إليه يظل قابلًا للتغيير. أما as const فتأكيد نوع في TypeScript: يجعل كل خصائص القيمة readonly وحرفية في نظام الأنواع. لا يجمد أي منهما الكائن وقت التشغيل؛ استخدم Object.freeze لذلك.
كيف أحصل على union type من مصفوفة نصوص؟
عرّف المصفوفة بـ as const، ثم فهرس نوعها بـ number: const roles = ["admin", "user"] as const; type Role = (typeof roles)[number]; يعطي "admin" | "user". دون as const تكون المصفوفة string[] ويكون الناتج مجرد string.