ל-typeof יש שתי משמעויות ב-TypeScript. בביטוי הוא האופרטור של JavaScript: הוא רץ, מחזיר מחרוזת כמו "string" או "number", ו-TypeScript מצמצם את המשתנה כשמשווים את המחרוזת הזו. במיקום של טיפוס הוא type query: typeof config נותן את הטיפוס הסטטי של המשתנה config.
השורה האחרונה מראה את ההבדל: typeof custom בתוך console.log הוא האופרטור של זמן הריצה ומדפיס object, ואילו typeof defaults אחרי type Options = הוא השאילתה של זמן הקומפילציה ולעולם לא מגיע ל-JavaScript.
שתי המשמעויות במבט אחד
typeof של זמן ריצה (JavaScript) | typeof כ-type query (TypeScript) | |
|---|---|---|
| איפה הוא מופיע | בכל ביטוי: if, return, console.log(...) | במיקומים של טיפוס: אחרי :, ב-type X = ..., בתוך <...> |
| דוגמה | typeof x === "string" | let y: typeof x; |
| מתי מחושב | כשהקוד רץ | על ידי הקומפיילר, ואז נמחק |
| תוצאה | אחת משמונה מחרוזות | טיפוס |
| שימוש | בדיקת ערך וצמצום הטיפוס שלו | גזירת טיפוס מתוך ערך |
TypeScript מבחין ביניהם לפי המיקום, כך שאותה מילת מפתח אף פעם לא מתכוונת לשניהם בבת אחת.
התוצאות של typeof בזמן ריצה
האופרטור של זמן הריצה מחזיר אחת משמונה מחרוזות. הריצו את הקוד כדי לראות מה ערכים נפוצים נותנים:
| ערך | התוצאה של typeof | הערה |
|---|---|---|
"hi", template strings | "string" | |
42, 3.14, NaN, Infinity | "number" | NaN הוא מספר; בדקו אותו עם Number.isNaN |
10n | "bigint" | |
true, false | "boolean" | |
undefined | "undefined" | |
Symbol("id") | "symbol" | |
| פונקציות, פונקציות חץ, מחלקות | "function" | מחלקה היא פונקציית constructor |
null | "object" | באג היסטורי ב-JavaScript |
{}, [], new Date(), new Map() | "object" | מערכים הם אובייקטים: השתמשו ב-Array.isArray |
new String("x"), new Number(1) | "object" | אובייקטים עוטפים, הימנעו מהם |
ב-JavaScript רגיל, typeof על משתנה שמעולם לא הוצהר מחזיר "undefined" במקום לזרוק שגיאה. ב-TypeScript קוד כזה לא מתקמפל: Cannot find name 'notDeclared'. (TS2304).
צמצום עם typeof
השוואה של typeof בתוך if, switch, אופרטור טרנרי או אחרי && מצמצמת את המשתנה בכל ענף. זו הדרך הסטנדרטית לטפל ב-union של טיפוסים פרימיטיביים, והבדיקה הראשונה שכדאי לנסות על unknown.
typeof x !== "string" מצמצם בכיוון השני, לכל מה שאינו string. והקומפיילר מכיר את שמונה המחרוזות האפשריות, כך ששגיאת כתיב בהשוואה היא שגיאת קומפילציה ולא בדיקה שתמיד מחזירה false בשקט:
הקומפיילר מדפיס index.ts(3,7): error TS2367: This comparison appears to be unintentional because the types '"bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined"' and '"strnig"' have no overlap. ב-JavaScript רגיל, שגיאת הכתיב הייתה שולחת בשקט כל מחרוזת למסלול של number. תקנו את האיות והקוד ידפיס text.
המלכודת של typeof null
מכיוון ש-typeof null הוא "object", TypeScript מצמצם את typeof x === "object" ל-object | null, לא ל-object. הוסיפו בדיקת null, אחרת קריאת מאפיין היא שגיאה (TS18047 'x' is possibly 'null'.).
typeof לא יכול להבחין בין מערכים, תאריכים, maps ואובייקטים רגילים: כולם "object". השתמשו ב-Array.isArray(x) למערכים וב-x instanceof Date למופעים של מחלקות. לא typeof ולא שום דבר אחר יכול לבדוק בזמן ריצה אם ערך תואם ל-interface, כי interfaces נמחקים; לשם כך כתבו פונקציית type guard שבודקת את המאפיינים.
typeof במיקומים של טיפוס
בטיפוס, typeof someVariable מעתיק את הטיפוס ש-TypeScript הסיק (או שהצהרתם) עבור המשתנה הזה. זה חוסך כתיבה ידנית של טיפוס עבור ערך שכבר קיים, ושומר על שניהם מסונכרנים.
const theme = {
primary: "#3178c6",
spacing: 8,
dark: false,
};
type Theme = typeof theme;
// { primary: string; spacing: number; dark: boolean }
function withSpacing(t: Theme, factor: number): Theme {
return { ...t, spacing: t.spacing * factor };
}
console.log(withSpacing(theme, 2)); // { primary: '#3178c6', spacing: 16, dark: false }
let userName = "Ada";
const fixed = "Ada";
type A = typeof userName; // string
type B = typeof fixed; // "Ada" (a const keeps its literal type)
התוצאה תלויה באופן שבו המשתנה הוצהר: let או מאפיין של אובייקט מורחבים (string, number), ו-const פרימיטיבי שומר על הטיפוס הליטרלי שלו. הוסיפו as const לאובייקט או למערך, ו-typeof ייתן טיפוסים ליטרליים לקריאה בלבד לכל העומק. typeof מקבל גם גישה למאפיין, typeof theme.spacing הוא number, אבל לא ביטויים שרירותיים: type T = typeof getTheme(); היא שגיאת תחביר (';' expected., TS1005). לטיפוס שקריאה מחזירה, השתמשו ב-ReturnType<typeof getTheme>.
keyof typeof: המפתחות של אובייקט כטיפוס
keyof typeof obj הוא השילוב הנפוץ ביותר. typeof הופך את האובייקט לטיפוס, keyof לוקח את המפתחות שלו, ומקבלים union של שמות המאפיינים שנשאר נכון גם כשהאובייקט משתנה.
אותו טריק על מערך קבוע נותן union של הערכים שלו: עם const roles = ["admin", "editor"] as const, הטיפוס (typeof roles)[number] הוא "admin" | "editor". בלי as const הוא היה רק string. העמוד keyof מסביר את keyof לבדו.
ReturnType<typeof fn> ו-typeof על מחלקות
טיפוסי עזר כמו ReturnType ו-Parameters מקבלים טיפוס של פונקציה. שם של פונקציה הוא ערך, ולכן העבירו אותו קודם דרך typeof:
כתיבת ReturnType<createUser> נכשלת עם 'createUser' refers to a value, but is being used as a type here. Did you mean 'typeof createUser'? (TS2749), וזה הקומפיילר שמבקש בדיוק את זה.
מחלקות הן המקרה היחיד שבו שם הוא גם ערך וגם טיפוס. User כטיפוס פירושו מופע של המחלקה; typeof User פירושו המחלקה עצמה, ה-constructor, כולל האיברים הסטטיים שלה:
class Point {
static origin = new Point(0, 0);
constructor(public x: number, public y: number) {}
}
const p: Point = new Point(1, 2); // an instance
const Ctor: typeof Point = Point; // the class (constructor + statics)
const q = new Ctor(3, 4); // q: Point
type Instance = InstanceType<typeof Point>; // Point
typeof מול instanceof מול type guard
| בדיקה | עובדת על | דוגמה |
|---|---|---|
typeof | טיפוסים פרימיטיביים, פונקציות, "האם זה אובייקט" | typeof x === "number" |
Array.isArray | מערכים | Array.isArray(x) |
instanceof | מופעים של מחלקות (Date, Error, המחלקות שלכם) | x instanceof Date |
in | unions של אובייקטים, לפי מאפיין | "email" in x |
| פונקציית type guard | interfaces, type aliases, כל דבר אחר | isUser(x) עם x is User |
בחרו את הכלי הצר ביותר שמתאים. למופעים של מחלקות ראו instanceof; למבנים שמתוארים על ידי interface, פונקציית type guard היא הבדיקה היחידה שזמינה בזמן ריצה.
שאלות נפוצות
מה עושה typeof ב-TypeScript?
שני דברים שונים, לפי המקום שבו הוא מופיע. בביטוי, typeof x הוא האופרטור של JavaScript: הוא רץ ומחזיר מחרוזת כמו "string" או "object", ו-TypeScript מצמצם את x כשמשווים את המחרוזת הזו. בהערת טיפוס, typeof x הוא type query: הקומפיילר מחשב אותו והוא נותן את הטיפוס הסטטי של המשתנה x, ונעלם מה-JavaScript שנוצר.
מה זה keyof typeof ב-TypeScript?
keyof typeof obj נותן את ה-union של שמות המאפיינים של אובייקט כטיפוסי string literal. typeof obj הופך את ערך האובייקט לטיפוס שלו, ו-keyof לוקח את המפתחות של הטיפוס הזה. עבור const colors = { red: "#f00", blue: "#00f" }, keyof typeof colors הוא "red" | "blue".
למה typeof null מחזיר "object"?
זה באג מהגרסה הראשונה של JavaScript שכבר אי אפשר לתקן בלי לשבור את הרשת. TypeScript מייצג אותו: אחרי typeof x === "object", x מצומצם ל-object | null, ולכן צריך לבדוק גם x !== null לפני שקוראים מאפיינים.
איך בודקים אם ערך הוא מערך ב-TypeScript?
השתמשו ב-Array.isArray(value), שמצמצם לטיפוס מערך. typeof לא יכול לעשות את זה: typeof [] הוא "object", בדיוק כמו עבור אובייקטים רגילים ועבור null.
מה ההבדל בין typeof ל-instanceof ב-TypeScript?
typeof בודק את הקטגוריה הפרימיטיבית של ערך ("string", "number", "function", "object"...). instanceof בודק אם אובייקט נוצר על ידי מחלקה או constructor מסוימים, כמו Date או class User משלכם. השתמשו ב-typeof לטיפוסים פרימיטיביים וב-instanceof למופעים של מחלקות; אף אחד מהם לא יכול לבדוק interface או type alias, שלא קיימים בזמן ריצה.