טייפסקריפט: פונקציות מתקדמות

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

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

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

const callToAPI = (fn => {
    // Does something
    fn();
});
const consoleLogger = (message) => { console.log(message); };

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

let callToAPI = (fn => {
    // Does something
    fn();
});

let consoleLogger = (message:string):void => {console.log(message)};

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

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

let callToAPI = (fn:(a: string) => void) => {
    // Does something
    fn('someString');
};

let consoleLogger = (message:string):void => {console.log(message)};

זה יכול להראות מבלבל – בגלל כל הסוגריים והחץ. אבל בגדול ככה נראית חתימה:

(a: string) => void)

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

אולי זה יהיה יותר ברור אם אני אשתמש ב-type ואפריד את כל הברדק הזה עבורכם:

type functionSignature =  ((a: string) => void);

let callToAPI = (fn:functionSignature) => {
    // Does something
    fn('someString');
};

let consoleLogger = (message:string):void => {console.log(message)};

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

הגדרת ארגומנטים עם אוברלואוד

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

const combineStrings = (a:string, b: string, c?:string) => {
    let result = a + b;
    if(c) {
        result  = a + b + c;
    }
    return result;
}

combineStrings('string1', 'string2');
combineStrings('string1', 'string2', 'string3');

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

const makeDate = (monthOrTimestamp, day, year) => {
  if (day !== undefined && year !== undefined) {
    return new Date(year, monthOrTimestamp, day);
  } else {
    return new Date(monthOrTimestamp);
  }
}

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

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

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

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


function makeDate(timestamp: number): Date;
function makeDate(month: number, day: number, year: number): Date;
function makeDate(monthOrTimestamp: number, day?: number, year?: number): Date {
  if (day !== undefined && year !== undefined) {
    return new Date(year, monthOrTimestamp, day);
  } else {
    return new Date(monthOrTimestamp);
  }
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3); // No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 

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

never

במדריך הקודם על פונקציה דיברנו על void, שאנו משתמשים בו בפונקציות שלא מחזירות ערך. אבל בג'אווהסקריפט כל פונקציה מחזירה ערך אפילו אם אין בה return. איזה ערך? undefined. אבל יש פונקציות שבאמת לא מחזירות דבר. למשל פונקציה שבה יש throw. פונקציה כזו יכולה להיות מוגדרת כ-never.

const fail = (msg: string): never => {
  throw new Error(msg);
}

unknown

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

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

const someAPICaller = ():any => {
    const result = {}; // Something that comes out of API. it can be WHATEVER
    return result;
};

let result = someAPICaller();
result.whatever(); // Will work

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

const someAPICaller = ():unknown => {
    const result = {}; // Something that comes out of API. it can be WHATEVER
    return result;
};

במאמר הבא אנו נדבר על גנריות.

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

מיקרו בקרים

הפעלת מסך oled קטנטן עם esp32

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

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