any ו-unknown מקבלים שניהם כל ערך. ההבדל הוא מה אפשר לעשות עם הערך אחר כך: any מאפשר לעשות כל דבר ולא בודק כלום, ואילו unknown כמעט לא מאפשר לעשות כלום עד שמוכיחים מה הערך.
בלי השורה של @ts-expect-error, הקריאה u.toUpperCase() היא שגיאת הקומפילציה TS18046. הבדיקה עם typeof מצמצמת את u ל-string, ובתוך הבלוק הזה כל מתודות המחרוזת זמינות.
any מול unknown במבט אחד
any | unknown | |
|---|---|---|
| מקבל כל ערך | כן | כן |
השמה ל-string, number, ... | כן, בלי בדיקה | לא (TS2322) |
| קריאת מאפיין, קריאה למתודה | כן, בלי בדיקה | לא (TS18046) |
| קריאה אליו כפונקציה | כן | לא |
חשבון והשוואה (x * 2, x + 1, x < 5) | כן | לא (TS18046) |
| צריך בדיקה לפני שימוש | לא | כן (typeof, instanceof, in, type guard) |
| השפעה על בדיקת הטיפוסים | כבויה עבור הערך ועבור כל מה שהוא נוגע בו | נשארת פעילה |
במונחים של תורת הטיפוסים, unknown הוא ה-top type: כל טיפוס ניתן להשמה אליו, והוא ניתן להשמה רק ל-unknown ול-any. any הוא פתח מילוט שניתן להשמה לשני הכיוונים, לכל דבר חוץ מ-never.
any מכבה את בדיקת הטיפוסים
ערך מטיפוס any זוכה לאמון עיוור. הקומפיילר מקבל שגיאות הקלדה, טיפוסים שגויים ומאפיינים חסרים, והטעויות מופיעות במקום זה בזמן ריצה.
הפלט מראה את הבעיה: משתנה עם הערת הטיפוס number מחזיק מחרוזת, והשורה האחרונה זורקת Cannot read properties of undefined (reading 'city'). בנוסף, any מתפשט. user.name הוא any, ולכן כל ערך שמחושב ממנו הוא any, וערך אחד בלי טיפוס יכול לכבות את הבדיקה רחוק מהמקום שבו הוא נכנס.
unknown מחייב לבדוק קודם
עם unknown, הקומפיילר מסרב לכל פעולה עד שהקוד מצמצם את הערך. הצמצום משתמש בבדיקות JavaScript רגילות, ובתוך כל ענף לערך יש את הטיפוס שנבדק.
הבדיקה value === null חייבת לבוא לפני בדיקת האובייקט, כי typeof null הוא "object". אחרי "id" in value, TypeScript יודעת שלאובייקט יש מאפיין id מטיפוס unknown, כי שום דבר לא אומר מה הוא מכיל. String() הופך אותו לטקסט באופן מפורש; כדי להשתמש בו כמספר צריך לבדוק אותו קודם עם typeof.
אפשר גם לדלג על הבדיקה עם assertion, value as string, והקומפיילר יקבל את זה. זו הבטחה בלי שום בדיקת זמן ריצה מאחוריה, ולכן עדיף להשתמש בבדיקה אמיתית; ראו type assertions.
ולידציה של JSON עם unknown
JSON.parse מוצהר כמחזיר any, ולכן התוצאה שלו מכבה בשקט את הבדיקה. תנו לתוצאה את הטיפוס unknown וכתבו type guard שבודק את המבנה לפני ששאר הקוד סומך עליו.
הקלט הראשון מדפיס dark at 14px, והשני נדחה כי חסר בו fontSize. עם any, הקלט השני היה ממשיך הלאה כ-Settings עם fontSize לא מוגדר. לסכמות גדולות, ספריית ולידציה עושה את אותה עבודה ומסיקה את הטיפוס בשבילכם.
noImplicitAny
any לא מגיע רק מאנשים שכותבים אותו. פרמטר בלי הערת טיפוס ובלי הקשר להסיק ממנו יהיה any גם כן. האפשרות noImplicitAny, ש-strict מפעיל, מדווחת על זה כשגיאה:
index.ts(2,17): error TS7006: Parameter 'x' implicitly has an 'any' type.
התיקון הוא הערת טיפוס, function double(x: number). גם כתיבה מפורשת של x: any עוברת קומפילציה, וזו בדיוק הנקודה: any נשאר גלוי בקוד, ואפשר לחפש אותו ולעבור עליו בסקירת קוד.
איפה any עדיין מתגנב
גם תחת strict, המקורות האלה מייצרים any בלי שהמילה מופיעה בקוד שלכם:
| מקור | מה מקבלים | מה לעשות |
|---|---|---|
JSON.parse(text) | any | הערת טיפוס unknown, ואז ולידציה |
response.json() עם טיפוסי הדפדפן (DOM) | Promise<any> | כמו JSON.parse (טיפוסי ה-fetch של Node כבר מחזירים Promise<unknown>) |
| חבילה בלי הגדרות טיפוסים | any לכל מה שמיובא ממנה (עם שגיאה, אלא אם מוסיפים הצהרה) | התקינו @types/... או כתבו קובץ .d.ts |
value as any | any | השתמשו ב-type guard או ב-assertion מדויק |
catch (e) כש-useUnknownInCatchVariables כבוי | any | strict הופך אותו ל-unknown; השאירו את זה כך |
המשתנה של catch הוא unknown תחת strict כי אפשר לזרוק כל דבר, לא רק אובייקטי Error. צמצמו אותו עם e instanceof Error לפני שקוראים את e.message.
מתי any מקובל
any לא אסור, אבל כל שימוש בו הוא נקודה שהקומפיילר כבר לא מגן עליה. שימושים סבירים:
- מיגרציה של בסיס קוד JavaScript, שבה
anyמסמן את מה שעדיין אין לו טיפוסים. - קוד שמערכת הטיפוסים לא מסוגלת לבטא היטב, כשהוא קטן ומוסתר מאחורי חתימת פונקציה עם טיפוסים.
- קוד בדיקות שמעביר בכוונה מידע שגוי.
לכל השאר, unknown מכסה את אותו מקרה של "אני לא יודע מה הטיפוס" ושומר על הבדיקות. צוותים רבים אוכפים את זה עם כלל ה-lint @typescript-eslint/no-explicit-any. טיפוס קרוב, Record<string, unknown>, הוא הבחירה הרגילה ל"אובייקט כלשהו עם ערכים לא ידועים".
שאלות נפוצות
מה ההבדל בין any ל-unknown ב-TypeScript?
שניהם מקבלים כל ערך. עם any אפשר לעשות עם הערך כל דבר (לקרוא מאפיינים, לקרוא לו כפונקציה, להשים אותו ל-number) והקומפיילר לא בודק כלום. עם unknown כמעט אי אפשר לעשות כלום עד שמצמצמים אותו בבדיקה כמו typeof x === "string". unknown משאיר את בדיקת הטיפוסים פעילה, ולכן הוא הבחירה הבטוחה יותר.
מתי להשתמש ב-unknown במקום ב-any?
בכל פעם שהטיפוס של ערך לא ידוע בזמן קומפילציה: JSON שפוענח, מידע מתשובת רשת, שגיאה שנתפסה, הקלט של פונקציית ולידציה. תנו לו את הטיפוס unknown וצמצמו אותו. השתמשו ב-any רק לעבודת מיגרציה קצרת טווח או לקוד שמערכת הטיפוסים לא מסוגלת לתאר.
מה המשמעות של "Object is of type 'unknown'"?
השגיאות TS18046 ('x' is of type 'unknown') ו-TS2571 (Object is of type 'unknown', כשהערך הוא לא שם פשוט, למשל load().id) אומרות שהשתמשתם בערך unknown כאילו יש לו טיפוס מסוים, למשל כשקראתם מאפיין או קראתם למתודה. בדקו קודם את הטיפוס (typeof, instanceof, Array.isArray, in או פונקציית type guard), והשתמשו בערך בתוך הענף המצומצם.
למה JSON.parse מחזיר any?
ההצהרה שלו בספרייה הסטנדרטית היא parse(text: string, ...): any, כי הקומפיילר לא יכול לדעת מה מחרוזת מכילה. התוצאה מכבה בשקט את הבדיקה לכל מה שהיא נוגעת בו. במקום זה, הוסיפו הערת טיפוס: const data: unknown = JSON.parse(text), ואז בצעו ולידציה לפני השימוש.
מה זה noImplicitAny?
אפשרות קומפיילר, חלק מ-strict, שמדווחת על שגיאה כשהצהרה הייתה מקבלת בשקט את הטיפוס any כי אין לה הערת טיפוס ואין ממה להסיק: TS7006 לפרמטר, TS7005 או TS7034 למשתנה שאי אפשר להבין את הטיפוס שלו. היא מונעת מ-any להופיע בלי שמישהו כתב אותו. any מפורש עדיין מותר.