Menu

TypeScript Map: Map<K, V>, get, set, has ואיטרציה

איך משתמשים ב-Map ב-TypeScript: יצירת Map<K, V> עם טיפוסים, למה get מחזיר V | undefined, set, has ו-delete, איטרציה לפי סדר ההכנסה, אובייקטים כמפתחות, המרה לאובייקטים ול-JSON ובחזרה, Map מול אובייקט מול Record, והגדרת טיפוסים ל-array.map().

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

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 גם קצרה יותר וגם בטוחה. non-null assertion, stock.get("apples")!, משתיק את השגיאה אבל לא מגן אם המפתח חסר.

המתודות של Map והטיפוסים שלהן

איברטיפוס על Map<K, V>הערות
new Map<K, V>(entries?)Map<K, V>entries: iterable של [K, V]
set(key, value)thisמוסיף או מחליף; ניתן לשרשור
get(key)V | undefinedundefined כשחסר
has(key)boolean
delete(key)booleantrue אם משהו הוסר
clear()voidמסיר הכול
sizenumberמאפיין, לא מתודה
keys(), values()איטרטורים של K, Vפרשו למערך: [...map.keys()]
entries(), for...ofאיטרטור של [K, V]לפי סדר ההכנסה
forEach((value, key) => ...)voidשימו לב: הערך בא ראשון

איטרציה על Map

Map עובר על הרשומות לפי סדר ההכנסה. for...of על ה-map מניב tuples מהצורה [key, value], בטיפוס [K, V].

הגדרה של מפתח קיים מעדכנת את הערך אבל שומרת על המיקום המקורי שלו בסדר.

אובייקטים כמפתחות וספירה

כל ערך יכול להיות מפתח, כולל אובייקטים ומערכים. מפתחות מושווים כמו ===: שני אובייקטים עם אותו תוכן הם מפתחות שונים. Map הוא גם הדרך המקובלת לספור פריטים או לקבץ אותם.

כדי להשתמש בתוכן של אובייקט כמפתח, גזרו ממנו מפתח מסוג מחרוזת או מספר, כמו user.id או `${x},${y}`.

Map מול אובייקט מול Record

Map<K, V>אובייקט / Record<string, V>
טיפוסי מפתחכל דבר, מושווה כמו ===string (מספרים הופכים למחרוזות), symbol
טיפוס מפתח חסרget מחזיר V | undefinedobj[key] הוא V אלא אם noUncheckedIndexedAccess מופעל
סדרסדר ההכנסהבעיקר סדר ההכנסה, אבל מפתחות דמויי מספר שלם באים ראשונים בסדר עולה
גודלmap.sizeObject.keys(obj).length
הוספה ומחיקה תכופותמותאם לכךלא מותאם לכך
JSONלא ישירות (JSON.stringify(map) הוא "{}")ישירות
תחביר ליטרלי, destructuringלאכן
מפתחות שעוברים בירושה בטעותאין"toString" in {} הוא true

כלל אצבע: Map לאוסף שהמפתחות שלו הם נתונים (מזהי משתמשים, מילים, רשומות cache) ומשתנים בזמן ריצה; טיפוס אובייקט או Record לקבוצה קבועה של מפתחות ידועים ולכל דבר שהולך ל-JSON או מגיע ממנו. הדף על dictionary משווה בין index signatures, Record ו-Map לחיפושים לפי מפתח מחרוזת.

המרת Map לאובייקטים ול-JSON

הרשומות של Map אינן מאפיינים, ולכן JSON.stringify לא רואה אותן. המירו דרך Object.fromEntries ו-Object.entries:

JSON.parse מחזיר any, ולכן as Record<...> מצהיר מה הנתונים אמורים להיות. זו לא בדיקה בזמן ריצה; אמתו JSON לא מהימן לפני שסומכים על הטיפוס הזה.

הגדרת טיפוסים ל-array.map()

הרבה חיפושים של "typescript map" מתכוונים למתודת המערך, שהופכת כל איבר ומחזירה מערך חדש. הטיפוס שלה מוסק מה-callback, כך שכמעט אף פעם אין צורך בהגדרות טיפוס:

הגדרת טיפוס ההחזרה של ה-callback ((u): Option => ...) היא הדרך הברורה ביותר לציין את טיפוס התוצאה: מאפיין חסר או עם שגיאת כתיב באובייקט המוחזר הופך אז לשגיאת קומפילציה ב-callback.

שאלות נפוצות

איך יוצרים 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 ישירות, ותומך בתחביר ליטרלי וב-destructuring. השתמשו ב-Map לאוספים דינמיים עם מפתחות, ובאובייקט למבנים קבועים ולנתוני JSON.

איך ממירים Map לאובייקט או ל-JSON ב-TypeScript?

Object.fromEntries(map) הופך Map<string, V> לאובייקט רגיל, ואז JSON.stringify יכול לבצע לו סריאליזציה. JSON.stringify(map) על ה-Map עצמו נותן "{}", כי הרשומות של Map אינן מאפיינים. הכיוון ההפוך הוא new Map(Object.entries(obj)).

איך מגדירים טיפוס ל-callback של array.map ב-TypeScript?

בדרך כלל אין צורך: items.map((item) => item.name) מסיק את item מהמערך ואת טיפוס התוצאה ממה שה-callback מחזיר. כדי לכפות טיפוס תוצאה, העבירו אותו כארגומנט טיפוס, items.map<string>(...), או הגדירו את טיפוס ההחזרה של ה-callback.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל