الـ Map في TypeScript هي Map المدمجة في JavaScript مع مفاتيح وقيم محددة الأنواع: Map<string, number> تربط نصوصًا بأرقام. أنشئها بـ new Map<K, V>()، ثم استخدم set وget وhas وdelete. تعيد get النوع V | undefined، لأن المفتاح قد يكون غير موجود.
وسائط النوع هي ما يجعل الـ Map مفيدة في TypeScript: كل استدعاء لـ set يُفحص، وكل get يعيد نوع القيمة. السطر الأخير خطأ ترجمة (TS2345)، ويُبقي @ts-expect-error الكتلة قابلة للتشغيل.
تتناول هذه الصفحة المجموعة Map. إذا كنت تبحث عن array.map() فستجده في القسم الأخير.
إنشاء Map
تأتي الأنواع من وسائط النوع، أو من العناصر الأولية، أو من تحديد نوع صريح. العناصر الأولية مصفوفة من tuples بالشكل [key, value]، أو أي شيء آخر ينتجها.
فخّان في الاستنتاج:
new Map()دون وسائط نوع ودون عناصر نوعهاMap<any, any>. لا يُفحص أي شيء تضعه فيها أو تأخذه منها. اكتب دائمًاnew Map<K, V>().- العناصر ذات أنواع القيم المختلفة لا تُستنتج كـ union.
new Map([["a", 1], ["b", "x"]])خطأ TS2769 (No overload matches this call). اكتب النوع:new Map<string, number | string>([...]).
get تعيد V | undefined
لا تستطيع الـ Map أن تضمن وجود مفتاح، لذلك نوع get هو V | undefined. مع strict يجب أن تتعامل مع undefined قبل استخدام القيمة كـ V:
index.ts(4,7): error TS2322: Type 'number | undefined' is not assignable to type 'number'.
Type 'undefined' is not assignable to type 'number'.
الحلول، من الأكثر شيوعًا إلى الأقل:
لا يتذكر TypeScript أن has أعادت true عندما تستدعي get لاحقًا. فحص نتيجة get مباشرة أقصر وآمن في الوقت نفسه. تأكيد عدم القيمة الفارغة، stock.get("apples")!، يُسكت الخطأ لكنه لا يحميك إذا كان المفتاح غير موجود.
دوال Map وأنواعها
| العضو | النوع في Map<K, V> | ملاحظات |
|---|---|---|
new Map<K, V>(entries?) | Map<K, V> | entries: كائن قابل للتكرار من [K, V] |
set(key, value) | this | يضيف أو يستبدل، ويمكن تسلسله |
get(key) | V | undefined | undefined عند غياب المفتاح |
has(key) | boolean | |
delete(key) | boolean | true إذا حُذف شيء |
clear() | void | يحذف كل شيء |
size | number | خاصية، وليست دالة |
keys()، values() | مكررات لـ K وV | انشرها في مصفوفة: [...map.keys()] |
entries()، for...of | مكرر لـ [K, V] | بترتيب الإدراج |
forEach((value, key) => ...) | void | لاحظ أن القيمة تأتي أولًا |
التكرار على Map
تُكرَّر الـ Map بترتيب الإدراج. تعطي for...of على الـ map أزواجًا بالشكل [key, value]، نوعها [K, V].
إسناد قيمة لمفتاح موجود يحدّث القيمة لكنه يُبقي المفتاح في موضعه الأصلي من الترتيب.
المفاتيح من نوع كائن والعدّ
أي قيمة يمكن أن تكون مفتاحًا، بما في ذلك الكائنات والمصفوفات. تُقارن المفاتيح كما تفعل ===: كائنان لهما المحتوى نفسه مفتاحان مختلفان. والـ Map أيضًا هي الطريقة المعتادة لعدّ العناصر أو تجميعها.
لاستخدام محتوى الكائن كمفتاح، اشتق منه مفتاحًا نصيًا أو رقميًا، مثل user.id أو `${x},${y}`.
الفرق بين Map والكائن وRecord
Map<K, V> | الكائن / Record<string, V> | |
|---|---|---|
| أنواع المفاتيح | أي نوع، وتُقارن كما في === | string (الأرقام تتحول إلى نصوص)، وsymbol |
| نوع المفتاح الغائب | تعيد get النوع V | undefined | obj[key] نوعه V ما لم يكن noUncheckedIndexedAccess مفعّلًا |
| الترتيب | ترتيب الإدراج | ترتيب الإدراج غالبًا، لكن المفاتيح الشبيهة بالأعداد الصحيحة تأتي أولًا بترتيب تصاعدي |
| الحجم | map.size | Object.keys(obj).length |
| الإضافة والحذف المتكرران | محسّنة لذلك | غير محسّن لذلك |
| JSON | ليس مباشرة (JSON.stringify(map) يعطي "{}") | مباشرة |
| الصياغة الحرفية والتفكيك | لا | نعم |
| مفاتيح موروثة عن غير قصد | لا يوجد | "toString" in {} قيمتها true |
قاعدة عملية: Map لمجموعة مفاتيحها بيانات (معرّفات مستخدمين، كلمات، عناصر ذاكرة مؤقتة) وتتغير وقت التشغيل، ونوع كائن أو Record لمجموعة ثابتة من المفاتيح المعروفة ولكل ما يُرسل إلى JSON أو يُقرأ منه. تقارن صفحة القواميس بين index signatures وRecord وMap للبحث بمفاتيح نصية.
تحويل Map إلى كائنات وJSON
عناصر الـ Map ليست خصائص، لذلك لا يراها JSON.stringify. حوّلها عبر Object.fromEntries وObject.entries:
يعيد JSON.parse النوع any، لذلك تصرّح as Record<...> بما يُتوقع أن تكون عليه البيانات. هذا ليس فحصًا وقت التشغيل، فتحقق من JSON غير الموثوق قبل أن تثق بذلك النوع.
تحديد أنواع array.map()
كثير من عمليات البحث عن "typescript map" تقصد دالة المصفوفات التي تحوّل كل عنصر وتعيد مصفوفة جديدة. يُستنتج نوعها من دالة الاستدعاء، لذلك نادرًا ما تحتاج إلى تحديد الأنواع:
تحديد نوع القيمة المُعادة من دالة الاستدعاء ((u): Option => ...) هو أوضح طريقة للتصريح بنوع النتيجة: عندها تصبح أي خاصية مفقودة أو مكتوبة خطأ في الكائن المُعاد خطأ ترجمة عند دالة الاستدعاء.
الأسئلة الشائعة
كيف أنشئ Map في TypeScript؟
مرّر نوع المفتاح ونوع القيمة إلى المُنشئ: const ages = new Map<string, number>(). عند تمرير عناصر أولية تُستنتج الأنواع: new Map([["ada", 36]]) نوعه Map<string, number>. أما new Map() دون أنواع ودون عناصر فنوعه Map<any, any>، وهذا يعطّل الفحص، لذلك حدد له الأنواع دائمًا.
لماذا تعيد Map.get القيمة undefined في TypeScript؟
نوع map.get(key) هو V | undefined لأن المفتاح قد لا يكون موجودًا. لا يربط TypeScript بين استدعاء سابق لـ map.has(key) واستدعاء لاحق لـ get، لذلك حتى بعد has عليك التعامل مع undefined: احفظ النتيجة وافحصها، أو استخدم قيمة افتراضية مع ??.
ما الفرق بين Map والكائن في TypeScript؟
الـ Map تقبل مفاتيح من أي نوع (بما فيها الكائنات)، وتحفظ ترتيب الإدراج، ولها الخاصية size، وهي مصممة للإضافة والحذف المتكررين. أما الكائن العادي أو Record<string, V> فمفاتيحه نصوص (ورموز symbol) فقط، ويتحول إلى JSON مباشرة، ويدعم الصياغة الحرفية والتفكيك. استخدم Map للمجموعات الديناميكية ذات المفاتيح، والكائن للأشكال الثابتة وبيانات JSON.
كيف أحوّل Map إلى كائن أو JSON في TypeScript؟
يحوّل Object.fromEntries(map) الـ Map<string, V> إلى كائن عادي، يستطيع JSON.stringify بعدها تحويله إلى نص. استدعاء JSON.stringify(map) على الـ Map نفسها يعطي "{}"، لأن عناصر الـ Map ليست خصائص. والتحويل العكسي هو new Map(Object.entries(obj)).
كيف أحدد نوع دالة الاستدعاء في array.map في TypeScript؟
غالبًا لا تحتاج إلى ذلك: في items.map((item) => item.name) يُستنتج نوع item من المصفوفة، ونوع النتيجة مما تعيده دالة الاستدعاء. لفرض نوع النتيجة مرّره كوسيط نوع، items.map<string>(...)، أو حدد نوع القيمة المُعادة من دالة الاستدعاء.