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

القاموس في TypeScript: Index Signatures وRecord وMap

لا يوجد في TypeScript صنف قاموس أو hashmap؛ تحدد نوع البحث بالمفتاح والقيمة باستخدام index signature أو Record<K, V> أو Map<K, V>. تعرّف على كل منها، وكيف تتحقق من وجود مفتاح، وتضيف وتحذف وتكرر، ولماذا يهم noUncheckedIndexedAccess في القواميس.

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

لا يوجد في TypeScript صنف مستقل للقاموس أو الـ hashmap. القاموس إما كائن عادي نوعه محدد بـ index signature، { [key: string]: number }، أو النوع نفسه مكتوبًا Record<string, number>، أو Map<string, number>. الثلاثة تخزن القيم بمفتاح، وتختلف في أنواع المفاتيح، وفي نوع المفاتيح الغائبة، وفي طريقة تحويلها إلى نص.

للمفاتيح النصية والبيانات ذات شكل JSON، الكائن بالنوع Record<string, T> هو الخيار المعتاد. للمفاتيح غير النصية، أو للعناصر التي تُضاف وتُحذف باستمرار، استخدم Map.

Index Signatures

تقول index signature، [key: KeyType]: ValueType، إن "أي مفتاح من هذا النوع يرتبط بقيمة من هذا النوع". اسم المفتاح (key، name، userId) للتوثيق فقط. يمكن أن تكون أنواع المفاتيح string أو number أو symbol أو أنماط template literal أو union منها.

تحوّل JavaScript مفاتيح الأرقام إلى نصوص، لذلك يبقى للكائن { [id: number]: string } مفاتيح نصية وقت التشغيل: Object.keys({ 1: "one" }) يعطي [ '1' ]. الـ index signature الرقمية تقيّد فقط طريقة الفهرسة في TypeScript.

Record<K, V>

Record<string, V> اختصار لـ { [key: string]: V }. وعند استخدام union من مفاتيح حرفية بدلًا من string يصبح قاموسًا ثابتًا يجب أن يحتوي على كل مفتاح:

حذف staging من urls خطأ ترجمة TS2741 (Property 'staging' is missing...)، وهذا ما يجعل Record بمفاتيح union جدول بحث مفحوصًا. ويجعل Partial كل قيمة من النوع number | undefined.

Map كـ Hash Map

تقبل الـ Map مفاتيح من أي نوع، وتحفظ ترتيب الإدراج، ولها size، وتحدد نوع المفاتيح الغائبة بصدق: تعيد get النوع V | undefined.

التحقق من وجود مفتاح

توجد عدة طرق للفحص، ولا تعني كلها الشيء نفسه:

الفحصيعمل معانتبه إلى
Object.hasOwn(obj, key)الكائناتمن ES2022؛ استخدم Object.prototype.hasOwnProperty.call(obj, key) مع الأهداف الأقدم
key in objالكائناتصحيح أيضًا للمفاتيح الموروثة مثل toString وconstructor
obj[key] !== undefinedالكائناتلا يميّز المفتاح الغائب من مفتاح مخزنة قيمته undefined
if (obj[key])الكائناتخاطئ أيضًا للقيم 0 و"" وfalse
map.has(key)Mapلا يضيّق نوع map.get(key) اللاحقة
map.get(key) !== undefinedMapتحفظ undefined نفسه كما في الكائنات

مشكلة المفاتيح الموروثة هي السبب في أن المفاتيح التي يقدمها المستخدمون مثل "constructor" أو "__proto__" تجعل الكائنات العادية خطرة كقواميس. أما الـ Map فليس فيها مفاتيح كهذه.

مشكلة نوع المفتاح الغائب

في index signature أو Record<string, T> تكون قراءة أي مفتاح من النوع T، حتى لو لم يكن المفتاح موجودًا. يسمح لك المترجم باستدعاء دوال على قيمة هي undefined وقت التشغيل:

خيار المترجم noUncheckedIndexedAccess يحل ذلك: معه يصبح نوع colors["grass"] هو string | undefined، ويصبح استدعاء toUpperCase خطأ ترجمة إلى أن تفحص القيمة. هذا الخيار ليس جزءًا من strict، لذلك يجب تفعيله بشكل منفصل في tsconfig.json؛ راجع الوضع الصارم لبقية الخيارات المجاورة له. والـ Map لا تعاني هذه الفجوة، لأن get تتضمن undefined دائمًا.

الإضافة والحذف والتكرار

يعمل delete مع خصائص index signature. أما مع خاصية مسماة إلزامية في نوع كائن فهو خطأ ترجمة TS2790، The operand of a 'delete' operator must be optional.

أيها تستخدم

الحاجةاستخدم
مفاتيح نصية، وقراءة JSON أو كتابتهRecord<string, T>
مجموعة ثابتة ومعروفة من المفاتيح، كلها إلزاميةRecord<"a" | "b", T>
خصائص مسماة إضافة إلى مفاتيح إضافية حرةنوع كائن مع index signature
مفاتيح هي كائنات، أو أرقام تريد إبقاءها أرقامًا، أو أي نوع غير نصيMap<K, V>
إضافة وحذف متكرران، أو حجم تقرؤه كثيرًاMap<K, V>
مفاتيح تأتي من المستخدمينMap<K, V> (لا مفاتيح موروثة)

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

كيف أنشئ قاموسًا في TypeScript؟

حدد نوع كائن عادي باستخدام index signature، const ages: { [name: string]: number } = {}، أو ما يكافئه Record<string, number>. ثم أضف العناصر بـ ages["ada"] = 36. للمفاتيح التي ليست نصوصًا، أو لمجموعة تكثر فيها الإضافة والحذف، استخدم new Map<string, number>().

هل يوجد HashMap في TypeScript؟

ليس بهذا الاسم. الـ Map المدمجة في JavaScript هي hash map: تخزن Map<K, V> أزواج مفتاح وقيمة مع بحث سريع بالمفتاح، وتحفظ ترتيب الإدراج، وتقبل أي نوع للمفتاح. والكائن العادي بالنوع Record<string, V> هو الخيار الشائع الآخر للمفاتيح النصية.

كيف أتحقق من وجود مفتاح في قاموس TypeScript؟

لقاموس من نوع كائن استخدم Object.hasOwn(dict, key) أو key in dict (التي ترى أيضًا الخصائص الموروثة مثل toString)، أو اقرأ القيمة وقارنها بـ undefined. مع Map استخدم map.has(key)، أو افحص نتيجة map.get(key) مباشرة، لأن has لا تضيّق نوع get اللاحقة.

ما الفرق بين index signature وRecord؟

{ [key: string]: T } وRecord<string, T> يصفان النوع نفسه. Record أقصر، ويمكن أن يأخذ أيضًا union من مفاتيح محددة، Record<"a" | "b", T>، الذي يشترط وجود كل مفتاح. أما index signature فيمكن دمجها مع خصائص مسماة في نوع كائن واحد، ويمكن أن يحمل مفتاحها اسمًا يوثّقه، كما في { [userId: string]: User }.

لماذا لا تعطي قراءة مفتاح غير موجود في القاموس خطأ؟

افتراضيًا، dict[key] على index signature أو Record<string, T> نوعه T، مع أن القيمة وقت التشغيل تكون undefined للمفتاح الغائب. فعّل noUncheckedIndexedAccess في tsconfig.json فيصبح النوع T | undefined، ما يفرض الفحص. هذا الخيار ليس جزءًا من strict.

Coddy programming languages illustration

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

ابدأ الآن