Menu

TypeScript type guard: בדיקת הטיפוס של אובייקט

Type guard הוא בדיקה בזמן ריצה ש-TypeScript מבין. למדו את ה-guards המובנים, איך כותבים guard משלכם עם type predicate מהצורה value is Type, איך בודקים אם אובייקט הוא מטיפוס מסוים, פונקציות assertion עם asserts, ואיך מאמתים נתונים מטיפוס unknown.

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

Type guard הוא בדיקה בזמן ריצה ש-TypeScript מבין, ולכן הוא מצמצם את הטיפוס בתוך הענף שנבדק. typeof, instanceof ו-in הם guards מובנים; לכל דבר אחר כותבים פונקציה שטיפוס ההחזרה שלה הוא type predicate, value is Type.

isUser מחזירה ערך בוליאני רגיל בזמן ריצה. טיפוס ההחזרה value is User אומר לקומפיילר מה תוצאה של true מוכיחה, ואז כל if (isUser(x)) מצמצם את x ל-User.

Type guards מובנים

הבדיקות האלה מצמצמות בלי שום פונקציית עזר:

Guardדוגמהמתי להשתמש
typeoftypeof x === "number"טיפוסים פרימיטיביים ופונקציות
instanceofx instanceof Dateמופעים של מחלקות
in"email" in xunions של אובייקטים, מאפיינים של אובייקטים מטיפוס unknown
Array.isArrayArray.isArray(x)מערכים
שוויוןx === null, x.kind === "circle"null/undefined, תגיות ליטרליות
Truthinessif (x)הסרת null ו-undefined

כולן רצות כ-JavaScript רגיל. מה ש-TypeScript מוסיף הוא הצמצום: הוא קורא את הבדיקה ומתאים את הטיפוס בכל ענף. הרשימה המלאה של הצורות נמצאת בעמוד type narrowing. Guard מותאם אישית מיועד לבדיקות שלא נכנסות בביטוי אחד, או שרוצים להשתמש בהן שוב.

כתיבת type predicate

ל-type predicate יש הצורה parameterName is Type, והוא מחליף את boolean כטיפוס ההחזרה. הצמצום עובד בשני הכיוונים: true מצמצם ל-Type, ו-false מסיר את Type מה-union.

העברת guard ל-filter נותנת מערך עם טיפוס נכון. מאז TypeScript 5.5 הקומפיילר גם מסיק predicate מפונקציות חץ פשוטות, כך ש-pets.filter((p) => p.kind === "cat") מחזיר Cat[] בלי guard עם שם.

הטיפוס של ה-predicate חייב להתאים לטיפוס הפרמטר: function f(x: string): x is number היא השגיאה TS2677, A type predicate's type must be assignable to its parameter's type.

הקומפיילר סומך על ה-guard שלכם

TypeScript בודק ש-guard מחזיר ערך בוליאני. הוא לא בודק שהערך הבוליאני נכון. Guard שמחזיר true עבור הערכים הלא נכונים גורם לטיפוסים לשקר, והתוכנית נכשלת בזמן ריצה בלי שום שגיאת קומפילציה.

data.price.toFixed(2) זורק TypeError: Cannot read properties of undefined (reading 'toFixed') בזמן ריצה. הקומפיילר קיבל את data.price כ-number כי ה-guard אמר כך. בדקו כל מאפיין ששאר הקוד מסתמך עליו, ושמרו על guards קטנים, בדוקים וקרובים לטיפוס שהם מתארים.

בדיקה אם אובייקט הוא מטיפוס מסוים

זו השאלה שמאחורי רוב ה-guards המותאמים: נתונים מגיעים כ-unknown (מ-JSON.parse, fetch, localStorage, הודעה) וצריך לדעת אם הם תואמים ל-interface שלכם. המתכון:

  1. typeof value === "object" && value !== null (אובייקט, לא null).
  2. "prop" in value לכל מאפיין נדרש. על unknown, in מוסיף את המאפיין לטיפוס כ-unknown.
  3. typeof value.prop === "..." (או guard מקונן) לטיפוס של כל מאפיין.
  4. Array.isArray(value.items) && value.items.every(isItem) למערכים.

עבור מבנים גדולים או מקוננים לעומק, כתיבה ידנית של הבדיקות האלה נעשית מייגעת. ספריות סכמה כמו Zod או Valibot מאפשרות לתאר את המבנה פעם אחת, ונותנות גם את הבדיקה בזמן ריצה וגם את הטיפוס של TypeScript.

פונקציות assertion: asserts value is Type

פונקציית assertion זורקת שגיאה אם הבדיקה נכשלת, ואחרת חוזרת כרגיל. טיפוס ההחזרה שלה הוא asserts value is Type (או asserts condition), וכל מה שבא אחרי הקריאה מצומצם, בלי צורך ב-if.

כלל אחד מכשיל אנשים: חייבים לקרוא לפונקציית assertion דרך שם עם טיפוס מפורש. פונקציית חץ ב-const בלי הערת טיפוס, const check = (v: unknown): asserts v is string => {...}, נותנת את השגיאה TS2775 במקום הקריאה, Assertions require every name in the call target to be declared with an explicit type annotation. השתמשו בהצהרת function, או הוסיפו לקבוע הערת טיפוס של פונקציה.

Guards מול assertions מול casts

כליבדיקה בזמן ריצה?מצמצםבמקרה של כישלון
Guard מובנה (typeof, in...)כןבתוך הענףעובר לענף השני
פונקציית value is Tכן (הקוד שלכם)בתוך הענףעובר לענף השני
פונקציית asserts value is Tכן (הקוד שלכם)אחרי הקריאהזורקת שגיאה
value as Tלאאת הביטויכלום: הטיפוס השגוי מתפשט

Type assertion (as) משנה את הטיפוס בלי לבדוק כלום. בגבול שבו נתונים מגיעים מבחוץ, guard או פונקציית assertion הם הגרסה הבטוחה של אותו רעיון.

Guards מבוססי this במחלקות

מתודה יכולה לצמצם את האובייקט שעליו היא נקראת עם this is Type. זה שימושי בהיררכיות של מחלקות:

class FileNode {
  constructor(public name: string) {}
  isDirectory(): this is DirectoryNode {
    return this instanceof DirectoryNode;
  }
}

class DirectoryNode extends FileNode {
  children: FileNode[] = [];
}

function count(node: FileNode): number {
  return node.isDirectory() ? node.children.length : 0; // node: DirectoryNode in the true branch
}

שאלות נפוצות

מה זה type guard ב-TypeScript?

כל בדיקה בזמן ריצה ש-TypeScript משתמש בה כדי לצמצם טיפוס: typeof x === "string", x instanceof Date, "id" in x, Array.isArray(x), או קריאה לפונקציה שטיפוס ההחזרה שלה הוא type predicate כמו x is User. בתוך הענף שנבדק, למשתנה יש את הטיפוס הצר יותר.

איך בודקים אם אובייקט הוא מטיפוס מסוים ב-TypeScript?

טיפוסים לא קיימים בזמן ריצה, ולכן בודקים את המאפיינים: כתבו פונקציה isUser(value: unknown): value is User שבודקת typeof value === "object", value !== null, וכל מאפיין נדרש עם in ו-typeof. אחרי if (isUser(x)), ל-x יש הטיפוס User. עבור מחלקות, x instanceof MyClass מספיק.

מה המשמעות של "value is Type" ב-TypeScript?

זה type predicate, שמשמש כטיפוס ההחזרה של פונקציה. בזמן ריצה הפונקציה עדיין מחזירה ערך בוליאני, אבל כשהיא מחזירה true, TypeScript מצמצם את הארגומנט ל-Type במקום הקריאה, וכשהיא מחזירה false הוא מצמצם אותו לשאר האיברים של ה-union. הקומפיילר לא מאמת את גוף הפונקציה, ולכן הבדיקה חייבת להיות נכונה.

מה ההבדל בין type guard לפונקציית assertion?

Type guard (x is T) מחזיר ערך בוליאני ומצמצם בתוך if. פונקציית assertion (asserts x is T) לא מחזירה כלום וזורקת שגיאה כשהבדיקה נכשלת, כך שכל מה שבא אחרי הקריאה מצומצם בלי if. השתמשו ב-guards להסתעפות וב-assertions למצבים של "זה חייב להתקיים, אחרת לעצור".

אפשר לבדוק אם אובייקט מממש interface ב-TypeScript?

לא ישירות: interfaces נמחקים ו-instanceof לא מקבל אותם. כתבו type guard שבודק את המאפיינים של ה-interface, או הוסיפו מאפיין תגית ליטרלי (kind: "user") והשוו אותו. ספריות סכמה כמו Zod מייצרות גם את הבדיקה וגם את הטיפוס מהגדרה אחת.

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

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

להתחיל