Menu

TypeScript Record: הסבר על Record<K, V> עם דוגמאות

Record<K, V> הוא טיפוס האובייקט שהמפתחות שלו הם K וכל הערכים שלו הם V. למדו Record עם מפתחות מחרוזת ועם מפתחות איחוד (כל מפתח חובה), Partial<Record>, לולאה על Record עם מפתחות עם טיפוס, המלכודת של מפתח חסר, ומתי להשתמש במקום זאת ב-index signature או ב-Map.

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

Record<K, V> הוא utility type מובנה לאובייקט שהמפתחות שלו מטיפוס K וכל הערכים שלו מטיפוס V. עם מפתחות string הוא מתאר מילון; עם איחוד של מפתחות ליטרליים הוא מתאר אובייקט שחייב להכיל בדיוק את המפתחות האלה.

Record קיים רק במערכת הטיפוסים. בזמן ריצה שני האובייקטים הם אובייקטי JavaScript רגילים, ולכן הם עובדים עם ליטרלי אובייקט, spread, JSON.stringify וכל דבר אחר שמקבל אובייקט.

תחביר והגדרה

Record<Keys, Value>

Keys חייב להיות משהו שיכול לשמש מפתח של אובייקט: string, number, symbol, איחוד של ליטרלים מסוג מחרוזת או מספר, או template literal type. Value יכול להיות כל טיפוס. ההגדרה כולה בספרייה הסטנדרטית של TypeScript היא שורה אחת, mapped type:

type Record<K extends keyof any, T> = {
  [P in K]: T;
};

keyof any הוא string | number | symbol, קבוצת כל טיפוסי המפתח האפשריים. [P in K]: T יוצר מאפיין אחד מטיפוס T לכל איבר של K. זה מסביר את שתי ההתנהגויות שבהמשך: K רחב כמו string מייצר index signature (כל מפתח), ו-K שהוא איחוד מייצר מאפיין חובה אחד לכל איבר.

מפתחות איחוד: כל מפתח הוא חובה

כשהמפתחות הם איחוד של ליטרלים, Record חייב לפרט כל אחד מהם, ולא אף מפתח אחר. זה הופך את הקומפיילר לרשימת תיוג:

הוסיפו "cancelled" ל-Status, ושני האובייקטים יפסיקו לעבור קומפילציה עד שתתנו לסטטוס החדש תווית וצבע. השמטת מפתח, או הוספת מפתח שלא נמצא באיחוד, היא שגיאת קומפילציה:

index.ts(4,7): error TS2741: Property 'error' is missing in type '{ idle: string; loading: string; success: string; }' but required in type 'Record<Status, string>'.
index.ts(11,54): error TS2353: Object literal may only specify known properties, and 'paused' does not exist in type 'Record<Status, string>'.

אותו דבר עובד עם string enum כטיפוס המפתח: Record<Color, string> דורש רשומה אחת לכל איבר של ה-enum.

Record<string, T> והמפתח החסר

עם מפתחות string, כל מפתח מותר, ו-TypeScript נותן לכל שליפה את הטיפוס T, גם למפתח שלא קיים. בזמן ריצה מפתח חסר נותן undefined:

זה הבאג הנפוץ ביותר עם Record. שלוש דרכים לטפל בו: לבדוק עם in או Object.hasOwn לפני הקריאה, להצהיר על הערך כ-V | undefined, או להפעיל את אפשרות הקומפיילר noUncheckedIndexedAccess, שמוסיפה | undefined לכל קריאה דרך index signature בפרויקט. ל-Record עם מפתחות איחוד אין את הבעיה הזו, כי מובטח שכל מפתח קיים.

Partial<Record<K, V>>: רק חלק מהמפתחות

כדי להשתמש באיחוד של מפתחות בלי לדרוש את כולם, עטפו את ה-Record ב-Partial. אז קריאות מחזירות V | undefined, וזה כן:

מפתח עם שגיאת כתיב כמו jp הוא עדיין שגיאה, וזה היתרון לעומת Record<string, string>.

איטרציה על Record

Object.keys, Object.values ו-Object.entries כולם עובדים. הבעיה היא טיפוס המפתח: Object.keys מחזיר string[] ו-Object.entries מחזיר [string, V][], ואף פעם לא את איחוד המפתחות שלכם:

TypeScript משאיר את המפתחות כ-string בכוונה: לאובייקט יכולים להיות בזמן ריצה יותר מאפיינים ממה שהטיפוס שלו מפרט, ולכן הבטחה של Plan[] הייתה לא בטוחה באופן כללי. לאובייקט שיצרתם מליטרל, כמו seats, ה-cast בטוח.

בניית Record מנתונים

Records הם התוצאה הרגילה של קיבוץ או של יצירת אינדקס ממערך. התחילו מאובייקט ריק מטיפוס Record ומלאו אותו:

Book["genre"] משתמש מחדש באיחוד מה-interface כטיפוס המפתח, ולכן הוספת ז'אנר ל-Book גורמת ל-byGenre לדרוש רשומה חדשה.

Record<string, unknown> ו-interfaces

Record<string, unknown> הוא טיפוס נפוץ ל"אובייקט כלשהו עם מפתחות מחרוזת". הוא מקבל ליטרלי אובייקט וערכים שהטיפוס שלהם הוגדר עם type alias, אבל interface נדחה:

index.ts(11,11): error TS2345: Argument of type 'User' is not assignable to parameter of type 'Record<string, unknown>'.
  Index signature for type 'string' is missing in type 'User'.

אפשר להרחיב interfaces באמצעות מיזוג הצהרות, ולכן TypeScript לא מניח שהם מתאימים ל-index signature; type aliases אי אפשר לפתוח מחדש, ולכן type User = { name: string } היה עובר. הפתרונות הרגילים הם לקבל במקום זאת object (עדיין אפשר לקרוא עליו ל-Object.keys), להפוך את הפונקציה לגנרית (<T extends object>(obj: T)), או להצהיר על User עם type (דף interface מול type מכסה את שאר ההבדלים).

Record מול index signature מול Map

Record<K, V>{ [key: string]: V }Map<K, V>
קיים בזמן ריצהלא, אובייקט רגיללא, אובייקט רגילכן, מחלקה
קבוצה קבועה של מפתחותכן, עם K שהוא איחודלאלא
טיפוסי מפתחstring, number, symbol, איחודי ליטרלים, תבניות templatestring, number, symbol, תבניות templateכל דבר, כולל אובייקטים
טיפוס שליפה של מפתח חסרV (עם מפתחות string)VV | undefined מ-get
שילוב עם מאפיינים בעלי שםדרך חיתוך &כן, באותו טיפוסלא
JSON ו-spreadכןכןלא, צריך להמיר קודם
גודלObject.keys(r).lengthObject.keys(o).lengthm.size
הוספה ומחיקה תכופותעובדעובדבנוי לזה

בחרו ב-Record עם מפתחות איחוד בכל פעם שקבוצת המפתחות ידועה: זו האפשרות היחידה שבודקת שכל מפתח קיים. למפתחות מחרוזת פתוחים, Record<string, V> ו-index signature ניתנים להחלפה, ובסיסי קוד רבים מעדיפים את Record בגלל הקריאות. פנו ל-Map כשמפתחות נוספים ומוסרים בזמן ריצה, כשהמפתחות אינם מחרוזות, או כשצריך את הגודל ואת סדר ההכנסה בלי עבודה נוספת.

שאלות נפוצות

מה זה Record ב-TypeScript?

Record<K, V> הוא utility type מובנה לאובייקט שהמפתחות שלו מטיפוס K וכל הערכים שלו מטיפוס V. Record<string, number> הוא אובייקט עם כל מפתח מחרוזת וערכים מספריים; Record<"en" | "de", string> הוא אובייקט עם בדיוק המפתחות en ו-de, ושניהם מחרוזות.

מה ההבדל בין Record ל-Map ב-TypeScript?

Record הוא טיפוס לאובייקט JavaScript רגיל, ולכן הוא עובד עם ליטרלי אובייקט, JSON ו-spread, והוא נעלם בזמן קומפילציה. Map היא מחלקה שקיימת בזמן ריצה עם get, set, has ו-size, שומרת על סדר ההכנסה לכל המפתחות, מקבלת כל טיפוס מפתח (גם אובייקטים), ו-get מחזיר V | undefined. השתמשו ב-Record לנתונים קבועים או בצורת JSON, וב-Map למפתחות שנוספים ומוסרים בזמן ריצה.

מה ההבדל בין Record<string, T> לבין { [key: string]: T }?

מבחינת ערכים הם אותו טיפוס: Record<string, T> מתפרש לטיפוס אובייקט עם index signature של מחרוזת. שני הבדלים קטנים: ל-index signature יכול להיות שם, והוא יכול לשבת ליד מאפיינים אחרים באותו טיפוס, ו-keyof Record<string, T> הוא string בזמן ש-keyof { [key: string]: T } הוא string | number.

איך עוברים בלולאה על Record ב-TypeScript?

השתמשו ב-Object.entries(record) לזוגות מפתח וערך, ב-Object.keys למפתחות וב-Object.values לערכים. המפתחות חוזרים כ-string ולא כ-K, כי לאובייקט יכולים להיות מפתחות נוספים בזמן ריצה. כשל-Record יש איחוד של מפתחות ידועים, בצעו cast: (Object.keys(r) as Array<keyof typeof r>).

איך הופכים רק חלק מהמפתחות של Record לחובה?

עם מפתחות איחוד, Record<K, V> דורש כל מפתח. עטפו אותו ב-Partial כדי להפוך את כולם לאופציונליים: Partial<Record<Lang, string>>. לשילוב, השתמשו בחיתוך: Record<"en", string> & Partial<Record<"de" | "fr", string>> דורש את en ומתיר את האחרים.

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

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

להתחיל