Menu

TypeScript Indexed Access Types: T[K], T[number] ועוד

indexed access type קורא את הטיפוס של מאפיין מתוך טיפוס אחר: Person["age"] הוא number. כאן תלמדו את T["key"], מפתחות מסוג union, T[keyof T], T[number] לאיברי מערך, אינדקסים של tuple, ו-(typeof arr)[number] כדי להפוך מערך const ל-union.

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

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. זו הדרך לומר ברמת הטיפוסים "כל ערך של האובייקט הזה".

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

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

להתחיל