Menu

null ו-undefined ב-TypeScript: בדיקות והאופרטורים ?. ו-??

עם strictNullChecks, null ו-undefined הם טיפוסים נפרדים ש-TypeScript מחייב אתכם לטפל בהם. כאן תלמדו איך בודקים אותם, optional chaining (?.), סימן השאלה הכפול (??) ו-??=, וההבדל בין מאפיין אופציונלי ל-| undefined.

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

כש-strictNullChecks מופעל (הוא חלק מ-strict), ל-null ול-undefined יש טיפוסים משלהם. string אף פעם לא יכול להיות null; ערך שעשוי להיות חסר צריך לומר זאת בטיפוס שלו, כמו string | null, ו-TypeScript מחייב אתכם לטפל במקרה הזה לפני שמשתמשים בערך.

אחרי הבדיקה, הקומפיילר יודע ש-name הוא string, ולכן .split מותר. שאר הדף מכסה את הדרכים לבצע את הבדיקה הזו ואת האופרטורים שמקצרים אותה.

strictNullChecks ושגיאות "Possibly Null"

כשטיפוס כולל null או undefined, TypeScript מסרב להשתמש בערך כאילו הוא תמיד קיים:

index.ts(3,22): error TS18047: 'name' is possibly 'null'.

הגרסה של undefined היא TS18048, 'x' is possibly 'undefined'. בלי strictNullChecks, null ו-undefined מותרים בכל טיפוס והקוד הזה מתקמפל, ואז קורס בזמן ריצה עם TypeError בפעם הראשונה ש-name הוא null. סוג הבאגים הזה הוא הסיבה העיקרית להשאיר את strict מופעל.

מאיפה הטיפוסים האלה מגיעים בקוד יומיומי:

מקורטיפוס
arr.find(...)T | undefined
map.get(key)V | undefined
מאפיין אופציונלי p?: TT | undefined בקריאה
פרמטר אופציונלי x?: TT | undefined בתוך הפונקציה
str.match(re)RegExpMatchArray | null
JSON.parse(text)any, ולכן שום דבר לא נבדק

בדיקת null ו-undefined

כל בדיקה למטה מצמצמת את הטיפוס בתוך הבלוק. בחרו את זו שמתאימה למה שאתם רוצים לפסול.

בדיקהמסירה מהטיפוס
x !== undefinedundefined
x !== nullnull
x != nullnull ו-undefined
typeof x !== "undefined"undefined
if (x)null ו-undefined, וגם מדלגת על הערכים 0, "", false, NaN

== null הוא המקום היחיד שבו שוויון רופף הוא הדרך המקובלת: הוא נכון בדיוק עבור null ו-undefined, ולא עבור שום דבר אחר. בדיקת truthiness הייתה מתייחסת למחרוזת הריקה כחסרה, וזה לעיתים קרובות באג.

Optional chaining: ?.

a?.b קורא את b אם a אינו null או undefined, ואחרת עוצר ומחזיר undefined. אותו אופרטור עובד גם לאינדקסים, a?.[i], וגם לקריאות, fn?.().

הטיפוס של bob.address?.city הוא string | undefined: optional chaining מוסיף undefined לתוצאה, ולכן בדרך כלל משלבים אותו עם ??. ההתנהגות בזמן ריצה היא JavaScript רגיל; ראו optional chaining לפרטים על short-circuiting.

סימן השאלה הכפול: ??

a ?? b מחזיר את a אלא אם הוא null או undefined, ואז הוא מחזיר את b. הוא מחליף את הביטוי הישן a || b, שמשליך גם את 0, "", false ו-NaN:

ערך שמאליleft || "d"left ?? "d"
null"d""d"
undefined"d""d"
0"d"0
"""d"""
false"d"false
NaN"d"NaN

מבחינת טיפוסים, ?? מסיר את null ו-undefined מהצד השמאלי ומאחד את השאר עם הצד הימני, ולכן אפשר להשים את scores.get("Linus") ?? 0 ל-number.

השמת nullish: ??=

a ??= b משים את b ל-a רק כש-a הוא null או undefined. האחים שלו ||= ו-&&= משימים כשהצד השמאלי הוא falsy או truthy.

retries: 0 שורד את ??=, ואילו ה-label הריק מוחלף על ידי ||=. אחרי opts.retries ??= 3, TypeScript מצמצם את opts.retries ל-number עד סוף הפונקציה.

מאפיינים אופציונליים מול | undefined

nickname?: string ו-nickname: string | undefined נקראים אותו דבר, אבל הם שונים בשאלה אם המפתח חייב להתקיים:

השתמשו ב-? כשהקוראים יכולים להשמיט את המאפיין, וב-| undefined כשאתם רוצים שכל קורא יעביר אותו במפורש, גם אם הערך הוא undefined. האפשרות exactOptionalPropertyTypes (לא חלק מ-strict) מהדקת את ? עוד יותר: nickname?: string דוחה אז את { nickname: undefined } ומקבל רק מפתח חסר או מחרוזת. פרמטרים אופציונליים של פונקציה (x?: number) מתנהגים כמו מאפיינים אופציונליים: בתוך הפונקציה x הוא number | undefined.

null או undefined: במה להשתמש

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

  • השתמשו ב-undefined (ובמאפיינים אופציונליים) עבור "לא הוגדר" בטיפוסים שלכם. זה מה ש-JavaScript מייצר כברירת מחדל: מאפיינים חסרים, ארגומנטים שהושמטו, חיפושים שלא מצאו ב-find וב-Map.get.
  • קבלו null במקום ש-API נותן לכם אותו: ב-JSON אין undefined, ו-String.prototype.match והרבה מתודות DOM מחזירות null.
  • בדקו עם == null כשערך יכול להיות כל אחד מהם.

גישה לאינדקס במערך היא הפער היחיד: users[5] מקבל את טיפוס האיבר גם כשהאינדקס מחוץ לטווח. האפשרות noUncheckedIndexedAccess (לא חלק מ-strict) מוסיפה | undefined לכל גישה לאינדקס כדי שהקומפיילר יתפוס גם את זה.

שאלות נפוצות

מה המשמעות של סימן השאלה הכפול ב-TypeScript?

a ?? b הוא האופרטור nullish coalescing מ-JavaScript. הוא מחזיר את a אלא אם a הוא null או undefined, ואז הוא מחזיר את b. בניגוד ל-||, הוא שומר ערכי falsy אחרים כמו 0, "" ו-false. TypeScript מסיר את null ו-undefined מהטיפוס של הצד השמאלי, כך שכש-a הוא string | undefined, a ?? "x" הוא string.

איך בודקים אם ערך הוא undefined ב-TypeScript?

משווים אותו: if (value !== undefined) { ... }. TypeScript מצמצם את הטיפוס בתוך הבלוק. כדי לפסול גם null וגם undefined בבדיקה אחת, השתמשו ב-value != null (שוויון רופף), המקום היחיד שבו ==/!= הם הדרך המקובלת. בדיקת truthiness (if (value)) גם מצמצמת אבל מדלגת על 0, "" ו-false.

מה המשמעות של "Object is possibly undefined"?

השגיאות TS18048 ('x' is possibly 'undefined') ו-TS18047 ('x' is possibly 'null'), או TS2532 (Object is possibly 'undefined') כשלערך אין שם פשוט, כמו ב-getUser().address.city, מגיעות מ-strictNullChecks: הטיפוס כולל undefined או null, והקוד משתמש בערך כאילו זה לא אפשרי. בדקו אותו קודם, השתמשו ב-optional chaining (x?.name), תנו ערך ברירת מחדל עם ??, או שנו את הטיפוס אם הערך באמת לא יכול להיות חסר.

מה ההבדל בין null ל-undefined ב-TypeScript?

הם שני טיפוסים נפרדים עם ערך אחד כל אחד. JavaScript משתמש ב-undefined לדברים שאף פעם לא הוגדרו (מאפיין חסר, ארגומנט שהושמט, Map.get על מפתח חסר), ו-APIs משתמשים ב-null ל"אין ערך" מכוון (JSON, הרבה מתודות DOM). TypeScript עוקב אחריהם בנפרד, ולכן string | null לא מקבל undefined. בהרבה בסיסי קוד בוחרים ב-undefined לקוד שלהם ומקבלים null רק בגבולות המערכת.

האם מאפיין אופציונלי זהה ל-| undefined?

לא בדיוק. name?: string אומר שהמאפיין יכול להיות חסר לגמרי, וקריאה שלו נותנת string | undefined. name: string | undefined אומר שהמאפיין חייב להיות קיים, גם אם הערך שלו הוא undefined. כש-exactOptionalPropertyTypes מופעל, name?: string גם מפסיק לקבל ערך undefined מפורש.

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

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

להתחיל