טיפוסי איחוד וחיתוך בטייפסקריפט¶
אחרי שלומדים טיפוסים בסיסיים כמו string ו-number, השלב הבא הוא ללמוד לשלב טיפוסים זה עם זה. שני הכלים המרכזיים לזה הם union (איחוד) ו-intersection (חיתוך), ולמרות שהם נראים דומים בתחביר, הם עושים דברים די שונים.
Union - "או זה או זה"¶
טיפוס union, עם הסימן |, אומר "הערך יכול להיות אחד מכמה אפשרויות":
type Status = "pending" | "completed" | "failed";
let orderStatus: Status = "pending";
orderStatus = "shipped"; // שגיאה - "shipped" לא באפשרויות
זה שימושי במיוחד במקום string גנרי, כי זה מגביל את הערכים האפשריים לרשימה סגורה וידועה, ועורך הקוד אפילו יציע לכם אוטוקומפליט עם האפשרויות המדויקות.
Union עובד גם עם טיפוסים מורכבים יותר, לא רק מחרוזות:
type ID = string | number;
function findUser(id: ID) {
// id יכול להיות string או number
}
findUser("abc123"); // תקין
findUser(42); // גם תקין
Intersection - "גם זה וגם זה"¶
טיפוס intersection, עם הסימן &, משלב כמה טיפוסים לאחד שחייב לכלול את כל השדות של כולם:
type WithId = { id: number };
type WithTimestamp = { createdAt: Date };
type Entity = WithId & WithTimestamp;
const item: Entity = {
id: 1,
createdAt: new Date(),
};
Entity חייב לכלול גם id וגם createdAt - שני הטיפוסים "מתחברים" לאחד גדול יותר. זה שימושי כשרוצים להרכיב טיפוס מכמה חלקים קטנים וברורים, במקום להגדיר הכל מחדש בכל פעם.
type ButtonProps = {
label: string;
onClick: () => void;
};
type WithVariant = {
variant: "primary" | "secondary";
};
type StyledButtonProps = ButtonProps & WithVariant;
הבחנה חשובה - union מצמצם, intersection מרחיב¶
זה הבלבול הכי נפוץ. עם union, יש לכם פחות ודאות על הערך - הוא יכול להיות אחד מכמה דברים, אז אי אפשר לגשת לשדות שלא משותפים לכל האפשרויות. עם intersection, יש לכם יותר דרישות - הערך חייב לכלול הכל בבת אחת.
type Circle = { radius: number };
type Square = { side: number };
function getArea(shape: Circle | Square) {
// shape.radius - שגיאה! shape יכול להיות Square בלי radius
}
function getCombined(shape: Circle & Square) {
shape.radius; // תקין - shape חייב את שני השדות
shape.side; // תקין
}
Discriminated Unions - הדפוס שהופך union לשימושי באמת¶
הבעיה בדוגמה של Circle | Square היא שאי אפשר לדעת בקוד איזה מהשניים קיבלתם. הפתרון הנפוץ הוא להוסיף שדה משותף שמזהה איזו וריאציה זו בדיוק - זה נקרא discriminated union:
type Circle = { kind: "circle"; radius: number };
type Square = { kind: "square"; side: number };
type Shape = Circle | Square;
function getArea(shape: Shape): number {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2; // כאן טייפסקריפט יודע שזה Circle
} else {
return shape.side ** 2; // וכאן שזה Square
}
}
השדה kind הוא ה"מבחין" (discriminant). כשבודקים אותו ב-if, טייפסקריפט "מצמצם" (narrows) את הטיפוס אוטומטית בתוך כל ענף - בתוך ה-if הראשון, הוא יודע בוודאות שמדובר ב-Circle, ולכן נותן גישה בטוחה ל-radius.
דוגמה מהחיים - מצבי טעינה בריאקט¶
דפוס נפוץ מאוד בריאקט הוא לדגמן מצבי טעינה עם discriminated union במקום כמה משתני בוליאני נפרדים:
type RequestState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; message: string };
function renderState(state: RequestState<User>) {
switch (state.status) {
case "loading":
return "טוען...";
case "success":
return state.data.name; // רק כאן data קיים, וטייפסקריפט יודע את זה
case "error":
return state.message;
default:
return null;
}
}
הגישה הזאת עדיפה על פני { loading: boolean, error: string | null, data: User | null }, כי היא הופכת מצבים בלתי אפשריים - כמו loading: true ו-data מלא בו זמנית - לבלתי ניתנים לייצוג בכלל. אם הטיפוס לא מאפשר את זה, אי אפשר בטעות לכתוב קוד עם באג כזה.
למה זה שווה את המאמץ¶
הכלים האלה נראים כמו תוספת תיאורטית, אבל בפרויקטים אמיתיים הם תופסים המון קטגוריות של באגים לפני שהקוד בכלל רץ - במיוחד discriminated unions, שמונעים ממכם לגשת לשדה שלא קיים במצב הנוכחי.
בקורס צד לקוח יש תרגול על דגימון מצבים עם union types, כולל בניית מנגנון שליפת נתונים אמיתי בריאקט עם טייפוס בטוח לגמרי.
לסיכום¶
Union (|) אומר "אחד מהאפשרויות האלה", ומצמצם מה שאפשר לדעת בוודאות. Intersection (&) אומר "כל האפשרויות ביחד", ומרחיב את הדרישות. discriminated unions, עם שדה מבחין משותף, הם הדרך הכי חזקה לדגמן מצבים שונים בצורה שטייפסקריפט יכול לעזור לכם לשמור עליה נכונה.
מתלבטים איך לדגמן מצב עם כמה אפשרויות בפרויקט שלכם? תשאלו בדיסקורד.