Menu

TypeScript union types: האופרטור | עם דוגמאות

Union type כמו string | number פירושו שערך יכול להיות כל אחד מכמה טיפוסים. למדו מה אפשר לעשות עם union (רק מה שכל האיברים תומכים בו), איך מצמצמים אותו, unions של ליטרלים ושל טיפוסי אובייקטים, וההבדל בין (A | B)[] לבין A[] | B[].

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

Union type מפרט אפשרויות עם |: ערך מטיפוס string | number הוא מחרוזת או מספר. בעזרת unions TypeScript מתאר ערכים שיכולים באופן לגיטימי להופיע ביותר מצורה אחת, והקומפיילר מחייב לבדוק איזו צורה יש לכם לפני שמשתמשים במשהו שייחודי לה.

בתוך כל ענף של בדיקת ה-typeof, ל-id יש טיפוס יחיד. לשלב הזה קוראים narrowing, והוא מה שהופך unions לשימושיים.

מותר רק מה שמשותף לכל האיברים

לפני הצמצום, אפשר להשתמש רק במה שכל איבר של ה-union תומך בו. toString() קיים גם על מחרוזות וגם על מספרים, ולכן זה תקין; toUpperCase() קיים רק על מחרוזות:

index.ts(3,16): error TS2339: Property 'toUpperCase' does not exist on type 'string | number'.
  Property 'toUpperCase' does not exist on type 'number'.

השורה השנייה מציינת את האיבר שחסר לו המאפיין. אותו כלל חל גם בכיוון ההפוך: אי אפשר להעביר ערך מטיפוס string | number לפרמטר מטיפוס string (TS2345), כי הוא עשוי להיות מספר. Union מקבל יותר ערכים, ובתמורה מאפשר לעשות איתם פחות עד שבודקים.

צמצום של union

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

בדיקהמצמצמתמתאימה ל
typeof x === "string"לטיפוס הפרימיטיביstring, number, boolean, bigint, symbol, undefined, function
x === null, x === "a"לערך שמושווהnull, undefined, איברים ליטרליים
Array.isArray(x)לאיבר שהוא מערךמערכים
x instanceof Dateלמחלקהמופעים של מחלקות
"meow" in xלאיברים שיש להם את המאפייןטיפוסי אובייקטים
x.kind === "circle"לאיבר עם התגית הזוdiscriminated unions
isCat(x) (מחזירה x is Cat)למה שהפונקציה אומרתכל דבר, לוגיקה מותאמת

הרשימה המלאה של צורות הצמצום נמצאת בעמוד type narrowing.

Unions של טיפוסים ליטרליים

Union של ערכים ליטרליים הוא קבוצה סגורה של ערכים מותרים. זה ה-union הנפוץ ביותר בקוד אמיתי:

type Status = "idle" | "loading" | "success" | "error";
type Dice = 1 | 2 | 3 | 4 | 5 | 6;
type Toggle = "on" | "off" | boolean; // boolean is itself true | false

let current: Status = "idle";
current = "loading";   // fine
current = "finished";  // error TS2322: Type '"finished"' is not assignable to type 'Status'.

השוואה לליטרל מצמצמת: אחרי if (current === "error"), ענף ה-else יודע ש-current הוא אחד משלושת האחרים. Unions של ליטרלים מחליפים enums בהרבה בסיסי קוד; ראו literal types על as const ועל איך גוזרים union כזה ממערך.

Unions של טיפוסי אובייקטים

כשהאיברים הם טיפוסי אובייקטים, מאפיינים שמשותפים לכולם זמינים ישירות. לשאר, בדקו שהמאפיין קיים עם in:

עבור unions של כמה מבני אובייקטים, הדפוס הנקי יותר הוא מאפיין ליטרלי משותף כמו kind: "cat" / kind: "fish". בדיקה של המאפיין הזה בלבד מצמצמת את האובייקט כולו, ואפשר לבדוק ש-switch עליו מכסה את כל המקרים. הדפוס הזה נקרא discriminated union.

מערכים ו-unions

המיקום של הסוגריים משנה את המשמעות לגמרי:

טיפוספירושערך לדוגמה
(string | number)[]מערך שכל אחד מהאיברים שלו הוא מחרוזת או מספר[1, "two", 3]
string[] | number[]מערך של מחרוזות בלבד, או מערך של מספרים בלבד["a", "b"]
string | number[]מחרוזת, או מערך של מספרים (| נקשר חלש יותר מ-[])"text"

במעבר על (string | number)[], כל איבר הוא ה-union וצריך צמצום, כמו ב-callback של reduce שלמעלה. מתודות כמו map ו-filter עובדות גם על string[] | number[], וה-callback מקבל string | number.

Unions עם null ו-undefined

ה-union הנפוץ מכולם הוא "ערך או כלום": string | null, User | undefined. זה מה ש-Array.prototype.find ו-Map.prototype.get מחזירים, ומאפיין אופציונלי name?: string נקרא כ-string | undefined. לטיפול בהם עם ?., ?? ובדיקות null יש עמוד משלו: null ו-undefined.

כדי להסיר איברים מ-union קיים ברמת הטיפוסים, השתמשו בטיפוסי העזר המובנים: Exclude<"a" | "b" | "c", "a"> הוא "b" | "c", ו-NonNullable<string | null> הוא string.

שאלות נפוצות

מה זה union type ב-TypeScript?

טיפוס שמורכב מכמה אפשרויות שמחוברות עם |. ערך מטיפוס string | number יכול להיות מחרוזת או מספר. הקומפיילר מאפשר להשתמש רק במה שמשותף לכל האיברים, עד שמצמצמים את הערך לאיבר אחד עם בדיקה כמו typeof value === "string".

למה TypeScript אומר שמאפיין לא קיים על union type?

כי לפחות לאיבר אחד של ה-union אין אותו. השגיאה TS2339, למשל Property 'toUpperCase' does not exist on type 'string | number', אומרת שהערך עשוי להיות מספר, ולמספר אין toUpperCase. צמצמו קודם (typeof, in, Array.isArray, instanceof, או בדיקה של מאפיין מבחין), ואז השתמשו במאפיין שייחודי לאיבר.

איך מצהירים על מערך שמחזיק יותר מטיפוס אחד?

שימו את ה-union בסוגריים: (string | number)[] או Array<string | number>, שבו כל איבר יכול להיות כל אחד מהטיפוסים. string[] | number[] זה משהו אחר: המערך כולו הוא רק מחרוזות או רק מספרים. בלי סוגריים, string | number[] פירושו מחרוזת או מערך של מספרים.

מה ההבדל בין union ל-intersection type?

Union A | B הוא ערך שהוא אחד מהטיפוסים, ולכן אפשר להשתמש רק במה שמשותף להם. Intersection A & B הוא ערך שהוא שניהם בבת אחת, ולכן יש לו את כל האיברים של שניהם. בטיפוסי אובייקטים, A | B מקבל יותר ערכים ו-A & B דורש יותר מאפיינים.

איך בודקים מאיזה טיפוס ערך של union?

השתמשו בבדיקה בזמן ריצה ש-TypeScript מבין: typeof x === "string" לטיפוסים פרימיטיביים, Array.isArray(x) למערכים, x instanceof Date למחלקות, "prop" in x למבני אובייקטים, או x.kind === "circle" כשהאיברים חולקים תגית ליטרלית. ללוגיקה מותאמת, כתבו פונקציית type guard שמחזירה x is T.

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

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

להתחיל