Utility types הם טיפוסים גנריים שמובנים ב-TypeScript והופכים טיפוס אחד לטיפוס אחר. במקום לכתוב טיפוס User שני שבו כל השדות אופציונליים, כותבים Partial<User>; במקום להעתיק שלושה שדות, Pick<User, "id" | "name">. הם גלובליים, כך שלא צריך לייבא אותם.
כש-User משתנה, כל ארבעת הטיפוסים הנגזרים משתנים איתו. הספרייה הסטנדרטית של TypeScript (lib.es5.d.ts) מצהירה על 22 utility types. הסעיפים הבאים מפרטים את כולם, מקובצים לפי סוג הטיפוס שהם עובדים עליו, עם קישור לעמוד המפורט כשיש כזה.
טיפוסי אובייקט: Partial, Required, Readonly, Pick, Omit, Record
| Utility type | מה הוא עושה | דוגמה |
|---|---|---|
Partial<T> | הופך כל property לאופציונלי | Partial<User> לנתונים של עדכון |
Required<T> | הופך כל property לחובה (מסיר את ?) | Required<Config> אחרי שהוחלו ערכי ברירת המחדל |
Readonly<T> | הופך כל property ל-readonly | Readonly<State> |
Pick<T, K> | משאיר רק את המפתחות K | Pick<User, "id" | "name"> |
Omit<T, K> | מסיר את המפתחות K | Omit<User, "password"> |
Record<K, V> | טיפוס אובייקט עם מפתחות K וערכים V | Record<"en" | "de", string> |
Required<T> הוא ההפך של Partial<T>; עמוד ה-Partial מסביר את שניהם, כולל ה-undefined המפורש ש-spread כמו זה מעביר הלאה.
טיפוסי union: Exclude, Extract, NonNullable
| Utility type | מה הוא עושה | דוגמה |
|---|---|---|
Exclude<U, M> | מסיר את חברי ה-union שניתן להציב ב-M | Exclude<"a" | "b" | "c", "a"> הוא "b" | "c" |
Extract<U, M> | משאיר את חברי ה-union שניתן להציב ב-M | Extract<string | number, number> הוא number |
NonNullable<T> | מסיר את null ואת undefined | NonNullable<string | null> הוא string |
שלושת אלה עובדים על unions, לא על אובייקטים. זה ההבדל המרכזי בינם לבין Pick ו-Omit, שמקבלים טיפוס אובייקט ורשימה של המפתחות שלו.
טיפוסי פונקציות ומחלקות: Parameters, ReturnType ועוד
| Utility type | מה הוא עושה | דוגמה |
|---|---|---|
ReturnType<F> | טיפוס ההחזרה של טיפוס פונקציה | ReturnType<typeof createStore> |
Parameters<F> | טיפוסי הפרמטרים כ-tuple | Parameters<typeof fetchPage>[0] |
ConstructorParameters<C> | הפרמטרים של הבנאי של מחלקה כ-tuple | ConstructorParameters<typeof Point> |
InstanceType<C> | טיפוס המופע שבנאי יוצר | InstanceType<typeof Point> |
ThisParameterType<F> | הטיפוס של פרמטר ה-this של פונקציה | ThisParameterType<typeof greet> |
OmitThisParameter<F> | טיפוס הפונקציה בלי פרמטר ה-this שלה | הטיפוס של greet.bind(obj) |
ThisType<T> | קובע את הטיפוס של this בתוך המתודות של object literal | משמש עם noImplicitThis ב-APIs בסגנון builder |
NoInfer<T> | מונע הסקה של פרמטר טיפוס מהמיקום הזה | fallback: NoInfer<C> |
צריך את typeof createOrder כי ה-utility types האלה מקבלים טיפוס, ו-createOrder הוא ערך. אותו דבר עם מחלקות: typeof Point הוא טיפוס הבנאי, ואילו Point לבד כטיפוס כבר מציין את טיפוס המופע.
NoInfer קובע מאיפה טיפוס גנרי מקבל את הטיפוס שלו:
בלי NoInfer, TypeScript היה מסיק את C משני הארגומנטים ומרחיב אותו ל-"red" | "green" | "blue", כך שטעות ההקלדה בערך ברירת המחדל הייתה מתקבלת.
טיפוסי מחרוזות: Uppercase, Lowercase, Capitalize, Uncapitalize
| Utility type | מה הוא עושה | דוגמה |
|---|---|---|
Uppercase<S> | ממיר טיפוס literal של מחרוזת לאותיות גדולות | Uppercase<"get"> הוא "GET" |
Lowercase<S> | ממיר אותו לאותיות קטנות | Lowercase<"GET"> הוא "get" |
Capitalize<S> | הופך את התו הראשון לאות גדולה | Capitalize<"name"> הוא "Name" |
Uncapitalize<S> | הופך את התו הראשון לאות קטנה | Uncapitalize<"Name"> הוא "name" |
ארבעת אלה מובנים בקומפיילר ולא כתובים ב-TypeScript, והם שימושיים במיוחד בתוך template literal types, למשל `on${Capitalize<E>}` לשמות של event handlers.
Promises: Awaited
| Utility type | מה הוא עושה | דוגמה |
|---|---|---|
Awaited<T> | הטיפוס שמקבלים מ-await, עם פריסה של promises מקוננים | Awaited<Promise<Promise<number>>> הוא number |
Awaited<ReturnType<typeof fn>> היא הדרך המקובלת לתת שם לטיפוס התוצאה של פונקציה async בלי להצהיר עליו בנפרד.
שילוב utility types
אפשר לקנן utility types זה בתוך זה. כמה שילובים חוזרים מספיק פעמים כדי שכדאי להכיר אותם בעל פה:
קוראים utility type מקונן מבפנים החוצה: Readonly<Pick<Post, "id" | "title">> קודם משאיר שני properties, ואחר כך הופך אותם לקריאה בלבד. מאותם חלקים בונים helper בשם PartialBy שהופך רק חלק מהמפתחות לאופציונליים; עמוד ה-Partial כותב אותו במלואו.
Utility types לא עושים כלום בזמן ריצה
כל utility type נמחק כשהקוד עובר קומפילציה. ערך מטיפוס Omit<User, "password"> עדיין יכול להכיל סיסמה בזמן ריצה, אם האובייקט שממנו הוא הגיע הכיל אחת:
הטיפוס רק מגביל את מה שהקוד שלכם רשאי לקרוא. כדי להסיר שדה מהנתונים, הוציאו אותו עם destructuring כמו בשורות האחרונות, וכדי למנוע שינויים בזמן ריצה השתמשו ב-Object.freeze, לא ב-Readonly. הטיפוסים המובנים הם mapped types ו-conditional types של שורה אחת, כך שבאותם כלים אפשר לכתוב גם utility types משלכם.
שאלות נפוצות
מה הם utility types ב-TypeScript?
טיפוסים גנריים שמגיעים עם TypeScript ומשנים טיפוסים אחרים: Partial<T> הופך כל property לאופציונלי, Pick<T, K> משאיר רק חלק מה-properties, ReturnType<F> מחזיר את טיפוס ההחזרה של פונקציה, וכן הלאה. הם מוצהרים בספרייה הסטנדרטית, כך שמשתמשים בהם בלי לייבא שום דבר.
האם צריך לייבא utility types?
לא. Partial, Omit, Record, ReturnType וכל השאר הם טיפוסים גלובליים מקובצי הספרייה המובנים של TypeScript. כתבו Partial<User> בכל מקום; לא צריך import ולא צריך חבילת npm.
אילו utility types מובנים ב-TypeScript?
22, כולם מוצהרים ב-lib.es5.d.ts: Partial, Required, Readonly, Pick, Omit, Record, Exclude, Extract, NonNullable, Parameters, ConstructorParameters, ReturnType, InstanceType, ThisParameterType, OmitThisParameter, ThisType, NoInfer, Awaited, Uppercase, Lowercase, Capitalize ו-Uncapitalize.
האם utility types משנים אובייקטים בזמן ריצה?
לא. הם רק מתארים טיפוסים ונמחקים מפלט ה-JavaScript. Omit<User, "password"> לא מוחק שום property בשם password, ו-Readonly<T> לא מקפיא שום דבר. כדי לשנות את האובייקט עצמו צריך לכתוב קוד: destructuring עם rest, Object.freeze וכדומה.
אפשר לכתוב utility types משלי?
כן. המובנים הם TypeScript רגיל: רובם mapped types או conditional types של שורה אחת ב-lib.es5.d.ts. type Nullable<T> = { [K in keyof T]: T[K] | null } הוא utility type מותאם אישית שנכתב באותה דרך.