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 | 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 מניב tuples מהצורה [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) הוא "{}") | ישירות |
| תחביר ליטרלי, 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.