indexed access type מחפש את הטיפוס של מאפיין בטיפוס אחר, עם אותו תחביר סוגריים מרובעים ש-JavaScript משתמש בו לגישה למאפיינים. Person["age"] הוא number, ו-Person["address"]["city"] הוא string.
היתרון הוא מקור אמת יחיד. Age אינו עותק שני של number: אם Person["age"] משתנה ל-string, כל טיפוס שנכתב כ-Person["age"] משתנה יחד איתו. indexed access types קיימים רק בזמן הקומפילציה; הם לא מייצרים JavaScript.
מפתחות מסוג union ו-T[keyof T]
האינדקס יכול להיות union של מפתחות. התוצאה היא ה-union של טיפוסי המאפיינים:
Product[keyof Product] היא הדרך לומר ברמת הטיפוסים "כל ערך של מאפיין ב-Product". בקשה של מפתח שלא קיים היא שגיאת קומפילציה: Product["price"] נותן error TS2339: Property 'price' does not exist on type 'Product'.
מאפיינים אופציונליים כוללים undefined
עם strictNullChecks (פועל תחת strict), הטיפוס של מאפיין אופציונלי כולל undefined, וכך גם ה-indexed access type שלו:
type Profile = { name: string; nickname?: string };
type Nick = Profile["nickname"]; // string | undefined
כדי להסיר את ה-undefined, עטפו אותו: NonNullable<Profile["nickname"]> הוא string.
טיפוסי איברי מערך עם T[number]
מערכים ממופתחים לפי מספרים, ולכן אינדקס לטיפוס מערך עם number נותן את טיפוס האיבר. זה עובד גם דרך כמה רמות:
זה שימושי כשטיפוס מגיע ממקום שאין לכם שליטה עליו, כמו לקוח API שנוצר אוטומטית: ApiResponse["data"]["users"][number] נותן שם לטיפוס המשתמש בלי שמישהו ייצא אותו.
Tuples: אינדקס לפי מיקום
אפשר לעשות אינדקס לטיפוס tuple עם מיקום מסוים, עם number בשביל ה-union של כל טיפוסי האיברים, ועם "length" בשביל האורך שלו כטיפוס ליטרל:
(typeof arr)[number]: union ממערך const
השימוש הנפוץ ביותר בעולם האמיתי: לשמור רשימה של ערכים מותרים כמערך (כדי שאפשר יהיה לעבור עליה בלולאה בזמן ריצה) ולגזור ממנה את טיפוס ה-union (כדי שהקומפיילר יבדוק אותה).
שני חלקים גורמים לזה לעבוד. as const הופך את המערך ל-tuple לקריאה בלבד של טיפוסי ליטרל; בלעדיו, ROLES הוא string[] ו-(typeof ROLES)[number] הוא סתם string. אחר כך typeof הופך את הערך לטיפוס, ו-[number] אוסף את האיברים שלו ל-union. הסוגריים אופציונליים (typeof ROLES[number] אומר אותו דבר), אבל הם מבהירים את סדר הפעולות.
בדיקה של מחרוזת מול המערך
ל-tuple לקריאה בלבד הזה יש הפתעה אחת. המתודה includes שלו מקבלת רק את טיפוס האיבר, ולכן אי אפשר להעביר לה מחרוזת שרירותית:
index.ts(6,25): error TS2345: Argument of type 'string' is not assignable to parameter of type '"admin" | "editor" | "viewer"'.
הרחיבו את המערך ל-readonly string[] לצורך הבדיקה, והפכו את הפונקציה ל-type guard כדי שבדיקה מוצלחת תצמצם את הערך:
התבנית הזו נותנת רשימה אחת לשני התפקידים: בדיקת זמן הריצה משתמשת במערך, והטיפוס נגזר ממנו, כך שהם לא יכולים להתרחק זה מזה.
Indexed access בגנריקס: T[K]
בתוך גנרי, T[K] עם K extends keyof T הוא הטיפוס של המאפיין שמי שקורא לפונקציה בחר:
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map((item) => item[key]);
}
const users = [
{ name: "Ada", age: 36 },
{ name: "Linus", age: 28 },
];
const names = pluck(users, "name"); // string[]
const ages = pluck(users, "age"); // number[]
האילוץ K extends keyof T הוא מה שהופך את T[K] לחוקי. בלעדיו, TypeScript לא יכול לדעת ש-K הוא מפתח של T ומדווח error TS2536: Type 'K' cannot be used to index type 'T'.
התחביר במבט אחד
| כתיבה | משמעות | דוגמה לתוצאה |
|---|---|---|
T["key"] | הטיפוס של מאפיין אחד | Person["age"] הוא number |
T["a" | "b"] | union של טיפוסי המאפיינים האלה | string | number |
T[keyof T] | union של כל טיפוסי המאפיינים | כל טיפוס של ערך |
T["a"]["b"] | מאפיין מקונן | Person["address"]["city"] |
Arr[number] | טיפוס איבר במערך | User[] נותן User |
Tup[0] | איבר ה-tuple במיקום מסוים | הטיפוס של האיבר הראשון |
Tup["length"] | אורך ה-tuple כליטרל | 3 |
(typeof arr)[number] | union של הערכים של מערך const | "admin" | "editor" |
T[typeof key] | אינדקס עם הטיפוס של קבוע | כמו T["name"] |
האינדקס הוא תמיד טיפוס. Person[key], כש-key הוא משתנה const, נכשל עם שתי שגיאות: error TS2538: Type 'key' cannot be used as an index type. ו-error TS2749: 'key' refers to a value, but is being used as a type here. Did you mean 'typeof key'?.
שאלות נפוצות
מה זה indexed access type ב-TypeScript?
טיפוס שנכתב T[K] ומחפש את הטיפוס של המאפיין K בטיפוס T. עם type Person = { name: string; age: number }, Person["age"] הוא number. הוא משתמש באותו תחביר סוגריים מרובעים כמו גישה למאפיין ב-JavaScript, אבל עובד על טיפוסים בזמן הקומפילציה.
איך מקבלים את הטיפוס של איבר במערך ב-TypeScript?
עושים אינדקס לטיפוס המערך עם number: עבור type Users = User[], Users[number] הוא User. לערך של מערך, משלבים עם typeof: (typeof users)[number].
איך הופכים מערך של מחרוזות לטיפוס union?
מצהירים על המערך עם as const כדי ש-TypeScript ישמור את ערכי הליטרל, ואז עושים לו אינדקס עם number: const roles = ["admin", "editor"] as const; type Role = (typeof roles)[number]; נותן "admin" | "editor". בלי as const טיפוס האיבר הוא סתם string.
למה אי אפשר להשתמש במשתנה כאינדקס ב-indexed access type?
האינדקס חייב להיות טיפוס. Person[key] עם const key = "name" נכשל (השגיאות TS2538 ו-TS2749) כי key הוא ערך. כתבו Person[typeof key], או השתמשו ישירות בליטרל: Person["name"].
מה המשמעות של T[keyof T]?
זה אינדקס של T עם ה-union של כל המפתחות שלו, ולכן התוצאה היא ה-union של כל טיפוסי המאפיינים שלו. עבור { a: string; b: number } זה string | number. זו הדרך לומר ברמת הטיפוסים "כל ערך של האובייקט הזה".