טייפסקריפט: סוגים מתקדמים

במדריך הזה נכנס יותר לעומק להגדרת סוגי הנתונים השונים בדרכים יותר מסודרות בטייפסקריפט.
לוגו טייפסקריפט

במאמרים הקודמים בסדרת המדריכים על טייפסקריפט למדנו על סוגי מידע בסיסיים ואכיפתם בפונקציות ובמשתנים, למדנו גם על בניית סביבת עבודה בסיסית ב-tsc. במדריך הזה אנו נמשיך את הלימוד בכל הנוגע לסוגים שונים.

Type Aliases

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

const squareSizeCalculator = (square: {x:number, y:number}):number => {
    return square.x*square.y;
}

let result = squareSizeCalculator({x:2,y:3});

console.log(result);

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

const userOutput = (userObject: {firstName: string, lastName: string, id: string | number, city: string, zip: number, phone: string}) {
    // do something
}

גם מתישהו אנו נצטרך אולי למחזר את האובייקט הזה. אולי הוא מופיע בעוד מקומות. בדיוק בשביל זה אנחנו יכולים להגדיר סוג של אובייקט (type) באופן נפרד. איך? ממש כמו משתנה, רק במקום const או let (שאף אחד לא יזכיר פה את var!!!) יהיה לנו את המשתנה הזה, שנקרא aliases. שם נגדיר את כל מה שצריך ונוכל להשתמש ב-aliases הזה איפה שנרצה. הנה דוגמה להמחשה:

type user = {
    firstName: string,
    lastName: string,
    id: string | number,
    city: string,
    zip: number,
    phone: string
}

const userOutput = (userObject: user) => {
    // do something
}

userOutput({
    firstName: 'Ran',
    lastName: 'Bar-Zik',
    id: '6382020',
    city: 'Petah Tikva',
    zip: 6382020,
    phone: '03-6382020'
});

שימו לב ש-aliases זה לא ג'אווהסקריפט. בקובץ המקומפל לא תראו את הגדרת ה-type בכלל. זה פשוט טייפסקריפט שקובע איך אובייקטים נראים.

אפשר לקבוע type לא רק לאובייקטים אלא גם באופן כללי. למשל:

type ID = number | string;

אני אוכל להשתמש ב-ID הזה בכל פעם שיש לי number | string. אם אי פעם ארצה לשנות את זה, אני אוכל במקום אחד. הנה דוגמה לשימוש:

type ID = number | string;

type User = {
    firstName: string,
    lastName: string,
    id: ID,
    city: string,
    zip: number,
    phone: string
}

וזה כבר מתחיל להיות מעניין, כי במקום להגדיר כל פעם ממשק עבור פונקציה, אני מגדיר באותו מקום.

Interfaces

יחד עם alias שאותו אנו מכירים יש לנו גם interface. הוא עובד באופן די דומה ל-type. לא משתמשים ב-= אלא במילה השמורה interface. הנה, כך:

interface User {
    firstName: string,
    lastName: string,
    id: ID,
    city: string,
    zip: number,
    phone: string
}

const userOutput = (userObject: User) => {
    // do something
}

userOutput({
    firstName: 'Ran',
    lastName: 'Bar-Zik',
    id: '6382020',
    city: 'Petah Tikva',
    zip: 6382020,
    phone: '03-6382020'
});

ההבדל העיקרי בינהם הוא ש-interface אפשר להרחיב עם interface אחר באמצעות extends. באופן די קל ופשוט. הנה דוגמה שתבהיר את הכל:

type ID = number | string;

interface User {
    firstName: string,
    lastName: string,
    id: ID,
    city: string,
    zip: number,
    phone: string
}

interface PremiumUser extends User {
    subsriptionId: string,
    subscriptionStatus: string
  }

const userOutput = (userObject: PremiumUser) => {
    // do something
}

userOutput({
    firstName: 'Ran',
    lastName: 'Bar-Zik',
    id: '6382020',
    city: 'Petah Tikva',
    zip: 6382020,
    phone: '03-6382020',
    subsriptionId: '6382020',
    subscriptionStatus: 'gold'
});

במקרה הזה יצרתי אינטרפייס חדש שנקרא PremiumUser שפשוט הוא user עם עוד שתי שדות רלוונטיים לסוג הזה.

שימו לב: מקובל להשתמש באות גדולה בהגדרת type alias או interface.

Type Assertions

בפוסט על הגדרות משתנים, אמרנו ש"גם אם לא הגדרתם סוג, טייפסקריפט תגדיר את סוג המשתנה לפי ההשמה הראשונה. כך למשל, אם הגדרתי את someVar שיקבל ערך 5, מבלי להגדיר לו סוג, אז אם בהמשך אני אנסה להכניס אליו ערך לא מספרי, אני אקבל שגיאת טייפסקריפט." זה קורה כמובן גם בפלט של פונקציות מובנות בשפה. טייפסקריפט פשוט תנחש את סוג הפלט שיוצא ולפעמים זה פחות מתאים לנו. זו הסיבה שאנו יכולים ב-override. הוא נעשה עם as. למשל (דוגמה מתוך הדוקומנטציה). הפונקציה getElementById היא פונקציה טבעית בג'אווהסקריפט (מבוסס דפדפן) מחזירה לנו אלמנט DOM. אם נשתמש בה באופן הזה:

const myCanvas = document.getElementById("main_canvas");

אז טייפסקריפט תנסה לנחש את הסוג שחוזר, שזה אלמנט DOM אז מדובר באובייקט מסוג HTMLElement. אבל אם אנו יודעים שאנו קוראים לאובייקט HTMLElement מסוג קנבס, אנו יכולים לצמצם את הסוג ל: HTMLCanvasElement. אבל איך עושים את זה? מגדירים לטייפסקריפט שבמקרה הזה היא לא מנחשת. במקרה הזה אנחנו אומרים לה מה הסוג. איך? עם המילה השמורה as ואז שם הסוג:

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

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

const data: object = ['a', 'b', 'c'];

// data.length; // Error

console.log((data as string[]).length); // 3

ההסבה הזו היא חד פעמית ואינה משפיעה על הקוד של ג'אווהסקריפט.

דרך נוספת לבצע השמה של משתנה היא באמצעות סוגריים משולשים (עם חיצים) באופן הבא:

const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");

או:

console.log((<Array<string>>data).length);

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

הגדרה של סוג קבוע

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

let a: 'world' = 'world';
a = 'hello'; // Type '"hello"' is not assignable to type '"world"'.

זה נשמע כמו הפיצ'ר הכי מטופש בעולם. כי מי בדיוק ירצה סוג שיכול להיות טקסט מסוג אחד? אבל אם נשלב את זה עם union נוכל להגיע למצב שבו אני יכול להגדיר למשתנה כמה סוגים. למשל – משתנה שיכול להכיל רק מידות של בגדים שהגדרנו מראש. למשל:

let a: 'small' | 'medium' | 'large';

a = 'small';
a = 'medium';
a = 'large';
a = 'big'; // Type '"bug"' is not assignable to type '"small" | "medium" | "large"'.

זה נחמד, לא?

הגדרה של ערך קבוע באובייקטים

לפעמים, בדיוק כמו במשתנים, אנו רוצים לקבע ערך קבוע בתכונה מסוימת באובייקט. למשל במקרה שבו יש לי אובייקט מסוג userObject ולו יש תכונה בשם userType. אני רוצה שלתכונה הזו יהיה אפשר לשים רק admin. מן הסתם, אני אבצע הגדרה של סוג קבוע כמו בסעיף הקודם. הנה דוגמה:

const userObject = { userType: 'admin' };

// Later on in the code

userObject.userType = 'user';

אבל שוד ושבר!נ זה לא יחזיר שגיאה. למרות שהגדרתי סוג קבוע. הפתרון? שימוש ב-as להגדרה קבועה. כך:

const userObject = { userType : 'admin' as 'admin'};

// Later on in the code

userObject.userType = 'user'; // Type '"user"' is not assignable to type '"admin"'.

אני יכול לעשות גם union פה כדי להגביל סוגים:

const userObject = { userType : 'admin' as 'admin' | 'user' as 'user'};

// Later on in the code

userObject.userType = 'something'; // Type '"something"' is not assignable to type '"user"'.

עד כאן בנוגע לסוגי נתונים ועבודה איתם. בגדול למדנו את כל הבסיס, עכשיו הגיע הזמן להתקדם הלאה.

במאמר הבא אנו נלמד על פונקציות עם טייפסקריפט. נכנס קצת יותר לעומק של איך טייפסקריפט מתנהג בפלט, קלט ובכלל בתוך מבני הפונקציה של ג'אווהסקריפט.

פוסטים נוספים שכדאי לקרוא

תמונה מצוירת של רובוט שמנקה HTML
יסודות בתכנות

סניטציה – למה זה חשוב

הסבר על טכניקה פשוטה וידועה מאד שאנו מפעילים על מידע לפני שאנחנו מציגים אותו ב-HTML באפליקציה או באתר.

למפתחי ובוני אתרי אינטרנט

מדריך לשאילתות יעילות ל Chat GPT

כל אחד יכול לשאול את GPT, אבל אם תרצו לשאול אותו שאלות על תכנות – יש כמה שיטות וטיפים ליעל את העבודה מולו.

גלילה לראש העמוד