טייפסקריפט: סוגים בסיסיים

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

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

כדי להתאמן, נפתח את TypeScript Sandbox וניצור פונקציה שמקבלת נתון בוליאני בלבד.

const someFunc = (value: boolean) => {
    console.log(value);
}

someFunc(true); 
someFunc(5); 

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

חוץ מבוליאני יש לנו גם מספר. בשם number. עם n קטנה. אם נגדיר את המשתנה שצריך להכנס לתוך פונקציה כמספר כל דבר אחר שנכניס יקבל שגיאה.

שגיאה של משתנה שהוא לא בוליאני

ואם יש מספר? יש גם string, גם עם s קטנה.

const someFunc = (value: string) => {
    console.log(value);
}

someFunc('a'); 
someFunc(5); // Argument of type 'number' is not assignable to parameter of type 'string'.

הערה חשובה: חשוב לשים לב שהסוגים השונים הם עם אותיות קטנות כי אותיות גדולות כמו Number או String הן מילים שמורות בג'אווהסקריפט. זה יעבוד לכם, אבל עדיף מאוד להשתמש בסוגים עם אותיות קטנות.

סוג any

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

const someFunc = (value: any) => {
    console.log(value);
}

someFunc('a'); 
someFunc(5); 
someFunc('true'); 
someFunc(undefined); 
someFunc(); // Expected 1 arguments, but got 0.

מערכים

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

const someFunc = (value: number[]) => {
    console.log(value);
}

או מערך של מחרוזות טקסט באמצעות הקוד הזה:

const someFunc = (value: string[]) => {
    console.log(value);
}
שגיאות שמתקבלות הודות למערך שנכנס לא נכון

או מערך כלשהו (לא אכפת לי מה) באמצעות any יחד עם מערך:

const someFunc = (value: any[]) => {
    console.log(value);
}

ניתן גם לציין גודל ספציפי של מערך באמצעות הגדרה מדויקת של האיברים. למשל מערך של 3 מספרים. אני אנסה להכניס מערך של 4 מספרים ואני אקבל שגיאה:

const someFunc = (value: [number,number,number]) => {
    console.log(value);
}

someFunc([1,2,3]); 
someFunc([1,2,3, 4]); // Argument of type '[number, number, number, number]' is not assignable to parameter of type '[number, number, number]'.
// Source has 4 element(s) but target allows only 3.

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

const someFunc = (value: Array<number>) => {
    console.log(value);
}

באמת מומלץ לעשות copy&paste מהדוגמאות כאן כדי לראות איך זה עובד.

הגדרות משתנים

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

let someVar:number = 5;

someVar = 'a'; // Type 'string' is not assignable to type 'number'.

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

let someVar = 5;

someVar = 'a'; // Type 'string' is not assignable to type 'number'.

ארגומנטים אפשריים

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

const someFunc = (value: number, otherValue?:string) => {
    console.log(value);
    if(otherValue) {
        console.log(otherValue);
    }
}

someFunc(1); 
someFunc(1, 'a'); 
someFunc(1, 2); // Argument of type '2' is not assignable to parameter of type 'string | undefined'.

זה אולי מבלבל בהתחלה, אבל ממש ממש לא נורא.

הגדרת פלט של פונקציה

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

const someFunc = ():string => {
    return 'aba';
}

const someOtherFunc = ():string => {
    return 111; // Type 'number' is not assignable to type 'string'.
}

אין לנו שום בעיה לשלב כמובן:

const multiplier = (someNumber: number, anotherNumber:number):number => {
    return someNumber*anotherNumber;
}

let result = multiplier(2,3);

אם הפונקציה היא פונקציה שלא מחזירה דבר, כדאי להגדיר את ה-return type שלה כ-void (ולא any) שאומר שהפונקציה לא מחזירה דבר.

const someOtherFunc = ():void => {
    console.log('I return nothing');
}

הגדרת אובייקטים

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

 {x:number, y:number}

ואיך זה נראה בקוד? ככה:

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

נסו להכניס ארגומנט לפונקציה שהוא לא אובייקט עם x ו-y שהם מספרים? תקבלו שגיאת טייפסקריפט. די פשוט ואלגנטי כשאתם משווים ל-guards שהייתם צריכים להכניס אם לא היה טייפסקריפט.

Union

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

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

const printId = (id: number | string) => {
  console.log("Your ID is: " + id);
}

printId(6382020); 
printId('6382020');
printId({id: '6382020'}); // Argument of type '{ id: string; }' is not assignable to parameter of type 'string | number'.
//  Type '{ id: string; }' is not assignable to type 'number'.

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

const weekDay = (val: number | string) => {
  switch (val) {
    case 1: 
      return 'Sunday';
    case 2: 
      return 'Monday';
    case 3: 
      return 'Tuesday';
    case 4: 
      return 'Wednesday';
    case 5: 
      return 'Thursday';
    case 6: 
      return 'Friday';
    default: 
      return 'Saturday';
  }
}

הכל טוב ויפה ואני יכול להשתמש גם במספרים וגם בטקסט, אבל אם אני אשתמש בטקסט אני אצטרך להמיר אותו למספר כי אחרת תמיד אני אקבל שהיום יום שבת (דיברנו על כך במאמר הקודם). אז נכניס את ההמרה למספר שנעשית בערך כך: val = parseInt(val)

const weekDay = (val: number | string) => {
  val = parseInt(val); 
  switch (val) {
    case 1: 
      return 'Sunday';
    case 2: 
      return 'Monday';
    case 3: 
      return 'Tuesday';
    case 4: 
      return 'Wednesday';
    case 5: 
      return 'Thursday';
    case 6: 
      return 'Friday';
    default: 
      return 'Saturday';
  }
}

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

Argument of type 'string | number' is not assignable to parameter of type 'string'.
  Type 'number' is not assignable to type 'string'.

למה זה קורה? כי parseInt אמורה לקבל רק מחרוזת טקסט. לא מספר. נכניס לה מספר? היא לא תתפקד היטב. אותו דבר יקרה אם אני אשתמש ב-Math.floor כדי לוודא שאם נכנס לי שבר (שזה מספר ולידי) כלום לא יישבר לי. val = Math.floor(val)

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

const weekDay = (val: number | string) => {
    if(typeof val == 'string') {
        val = parseInt(val); 
    } else {
        val = Math.floor(val);
    }
    switch (val) {
    case 1: 
        return 'Sunday';
    case 2: 
        return 'Monday';
    case 3: 
        return 'Tuesday';
    case 4: 
        return 'Wednesday';
    case 5: 
        return 'Thursday';
    case 6: 
        return 'Friday';
    default: 
        return 'Saturday';
    }
}

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

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

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

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