value as Type הוא type assertion: הוא אומר ל-TypeScript להתייחס ל-value כאל Type. אנשים קוראים לזה cast, אבל זו רק הוראה לקומפיילר. היא נמחקת מפלט ה-JavaScript, לא ממירה כלום ולא בודקת כלום בזמן ריצה.
זה השימוש הטיפוסי: אתם יודעים על ערך יותר ממה שהקומפיילר יכול לדעת (כאן, את המבנה של JSON מסוים), ואתם אומרים את זה. אם טעיתם, שום דבר לא מזהיר אתכם. הסעיפים הבאים מראים מה זה אומר ומתי בדיקה בזמן ריצה היא הבחירה הטובה יותר.
as ותחביר הסוגריים המשולשים
יש שתי דרכים לכתוב את אותו assertion:
const someValue: unknown = "hello";
const a = someValue as string; // as syntax
const b = <string>someValue; // angle-bracket syntax, same meaning
הצורה עם הסוגריים המשולשים אסורה בקובצי .tsx, שם <string> ייקרא כתגית JSX. השתמשו ב-as בכל מקום, והשאלה לא תעלה אף פעם. Assertions נקשרים בחולשה, ולכן עטפו אותם בסוגריים כשממשיכים את הביטוי: (value as string).length.
Assertions לא ממירים ערכים
זה החלק שגורם לבאגים אמיתיים. Assertion משנה את מה שהקומפיילר מאמין לגבי ערך, לא את הערך עצמו:
הקומפיילר מאמין ש-asserted הוא number, ולכן asserted + 1 עובר את בדיקת הטיפוסים כחישוב אריתמטי. בזמן ריצה הוא עדיין המחרוזת "42", ו-JavaScript משרשר. כדי לשנות את הטיפוס של ערך, המירו אותו: Number(x), String(x), Boolean(x), BigInt(x), new Date(x). העמוד המרת מחרוזת למספר משווה בין פונקציות ההמרה.
| מה רוצים | מה כותבים | השפעה בזמן ריצה |
|---|---|---|
| לומר לקומפיילר טיפוס שאתם יודעים | x as T | אין |
| להפוך מחרוזת למספר | Number(x), parseInt(x, 10) | ממיר |
| להפוך כל דבר למחרוזת | String(x), `${x}` | ממיר |
| לבדוק את הטיפוס קודם | type guard, typeof, instanceof | בודק |
מה הקומפיילר מתיר
as אינו בלתי מוגבל. TypeScript מתיר x as T כשאחד הטיפוסים ניתן להשמה לשני: הרחבה ("a" as string, dog as Animal) וצמצום (animal as Dog, unknown as User) תקינים שניהם. כשאין שום חפיפה בין הטיפוסים, הוא מסרב:
הקומפיילר מדווח index.ts(3,11): error TS2352: Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. ההודעה עצמה מציינת את פתח המילוט: input as unknown as number. ה-assertion הכפול הזה מתקמפל, והוא שגוי בזמן ריצה בדיוק כמו הדוגמה שלמעלה. כשנראה לכם שאתם צריכים אותו, התיקון הנכון הוא בדרך כלל המרה (Number(input)) או טיפוס אחר.
כלל החפיפה מקל עם אובייקטים. אובייקט ליטרלי שיש לו חלק מהמאפיינים מתקבל, וכך as מעביר בשקט אובייקטים חלקיים:
הערת טיפוס (const draft: User = { name: "Ada" }) או satisfies User היו מדווחות על ה-email החסר (TS2741). השתמשו ב-as על אובייקט ליטרלי רק כשאתם באמת מתכוונים להשלים אותו מאוחר יותר, והעדיפו לבנות את האובייקט השלם.
as const זה משהו אחר
as const נראה כמו assertion, אבל הוא עושה את ההפך מהקלה: הוא הופך ליטרל לצר ככל האפשר. מחרוזות נשארות טיפוסים ליטרליים, מערכים הופכים ל-tuples לקריאה בלבד, ומאפייני אובייקטים הופכים ל-readonly.
הוא בטוח, כי הוא מתאר את הליטרל בדיוק ולא טוען משהו שהקומפיילר לא יכול לראות. (ה-sizes as readonly string[] שבתוך isSize הוא assertion מרחיב, גם הוא בטוח: הוא מאפשר ל-includes לקבל כל מחרוזת.) ראו literal types להרחבה.
מתי type guard הוא הכלי הטוב יותר
as הוא טענה; type guard הוא בדיקה. בגבול שבו נתונים מגיעים מחוץ לקוד שלכם (JSON, fetch, localStorage, קלט של משתמש, הודעה), הטענה עלולה להיות שקרית, ו-assertion הופך שגיאה ברורה בגבול לשגיאה מבלבלת במקום אחר.
מדריך כללי לכלים שנראים דומים:
| כלי | בודק בזמן קומפילציה | בודק בזמן ריצה | מתי להשתמש |
|---|---|---|---|
הערת טיפוס const x: T = ... | כן, במלואו | לא | כשאתם בונים את הערך בעצמכם |
satisfies T | כן, במלואו, ושומר את הטיפוס שהוסק | לא | אובייקטים ליטרליים, הגדרות |
as T | רק "האם הטיפוסים חופפים" | לא | כשאתם יודעים יותר מהקומפיילר |
x! | מסיר רק null / undefined | לא | כשאתם יודעים שערך מוגדר |
Type guard x is T | גוף ה-guard הוא קוד רגיל | כן | נתונים מבחוץ |
נשארים שני שימושים טובים ל-as: צמצום של משהו שהקומפיילר לא יכול לעקוב אחריו (רשומה ב-Map שהגדרתם שתי שורות קודם, ערך מספרייה בלי טיפוסים), וקוד בדיקות שבונה fixtures חלקיים. שמרו עליהם קטנים וקרובים למקום שבו אתם יודעים שהטענה נכונה.
שאלות נפוצות
מה עושה as ב-TypeScript?
value as Type הוא type assertion: הוא אומר לקומפיילר להתייחס ל-value כאל Type מכאן והלאה. הוא נמחק מה-JavaScript המקומפל, ולכן לא מבצע שום המרה ושום בדיקה בזמן ריצה. אם ה-assertion שגוי, התוכנית נכשלת מאוחר יותר, בכל מקום שבו משתמשים בטיפוס השגוי.
איך עושים cast לטיפוס ב-TypeScript?
ל-TypeScript אין casts בזמן ריצה. השתמשו ב-as (או בצורה הישנה <Type>value) כדי לשנות את הטיפוס הסטטי כשאתם יודעים יותר מהקומפיילר. כדי להמיר ערך באמת, קראו לפונקציה: Number("42"), String(42), Boolean(x), new Date(text).
מה המשמעות של "as unknown as" ב-TypeScript?
Assertion כפול. TypeScript מסרב ל-x as T כשאין שום חפיפה בין שני הטיפוסים (שגיאה TS2352), ומעבר קודם דרך unknown עוקף את הבדיקה הזו, כי אפשר לעשות assertion של כל דבר אל unknown וממנו. זה מכבה לגמרי את בדיקת הטיפוסים עבור הערך הזה, ולכן שמרו את זה לבדיקות ולקוד שבו וידאתם את הטיפוס בדרך אחרת.
מה ההבדל בין as לסוגריים משולשים ב-TypeScript?
אין הבדל במשמעות: <string>value ו-value as string הם אותו assertion. אי אפשר להשתמש בצורה עם הסוגריים המשולשים בקובצי .tsx, כי היא מתנגשת עם JSX, ולכן כולם משתמשים ב-as.
מה ההבדל בין as ל-satisfies?
as דורס את הטיפוס שהוסק ובודק מעט מאוד (מאפיינים חסרים מותרים). satisfies בודק את הערך מול טיפוס, מדווח על מאפיינים חסרים או עודפים, ושומר את הטיפוס המדויק שהוסק. העדיפו satisfies לאובייקטים ליטרליים, ו-as רק כשאתם באמת יודעים יותר מהקומפיילר.