יתרונות של TypeScript למתכנתי JavaScript

26/10/2022

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

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

1. מה TypeScript נותן לנו כמפתחי JavaScript

טייפסקריפט היא Superset של JavaScript. זה אומר שכל קובץ JavaScript שתיקחו הוא גם קובץ טייפסקריפט תקני. בנוסף VS Code משתמש בכל הכלים של טייפסקריפט אוטומטית גם בעבודה על קבצי js וגם בעבודה על קבצי ts.

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

הדברים המרכזיים שנקבל מ TypeScript בתור מפתחי ווב הם:

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

  2. עזרה בשינוי הקוד - כדי לוודא ש Refactor מסוים לא שובר קוד במקומות שלא חשבנו עליהם, וכדי לתקן אוטומטית את מה שאפשר.

  3. עזרה בכתיבת הקוד - כדי להציע לנו השלמות רלוונטיות במהלך הכתיבה.

בואו נראה דוגמאות לשלושת הסעיפים.

2. עזרה בבדיקת הקוד

אני מתחיל עם פרויקט TypeScript חדש שיצרתי באמצעות vite עם הפקודה:

$ npm create vite@latest

ובחרתי בתפריטים Vanilla TypeScript.

אני יוצר קובץ חדש בתיקיית src בשם utils.ts עם התוכן הבא:

export function twice(x: number) {
  return x * 2;
}

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

שומר את הקובץ וממשיך ל main.ts שכבר קיים בפרויקט. שם בשורה 4 אני כותב:

alert(twice(10));

אני רואה שמתחת למילה twice מופיע לי קו אדום שאומר ש VS Code לא מזהה את המילה. כשאני משתהה עם הסמן מעל המילה twice אני מקבל את ההודעה:

Cannot find name 'twice'.ts(2304)

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

  1. Add import from utils

  2. Add missing function declaration 'twice'

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

נבחר באפשרות Add import from utils, והקו האדום נעלם. אבל עכשיו בואו ניקח את זה עוד צעד קדימה - אני יודע שהפונקציה מצפה לקבל מספר, וגם typescript יודע את זה. נשנה את הקריאה ל:

alert(twice("yay"));

ושוב קו אדום, הפעם מתחת למילה yay. ההודעה הפעם:

Argument of type 'string' is not assignable to parameter of type 'number'.ts(2345)

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

'twice' is declared but its value is never read.ts(6133)

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

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

נתחיל עם הספריות המובנות ונוסיף את הבלוק הבא בתחילת הקובץ main.ts:

const headers = document.querySelector('h1,h2,h3,h4,h5,h6');
headers.forEach(h => {
  console.log(h.textContent);
});

רציתי לקחת את כל הכותרות, אבל השתמשתי בפונקציה הלא נכונה - במקום להשתמש ב querySelectorAll קראתי ל querySelector. טייפסקריפט מזהה ש querySelector מחזירה אלמנט DOM יחיד שלא כולל את הפונקציה forEach ולכן מסמן לי שלושה קווים אדומים: מתחת למילה headers, מתחת למילה forEach ומתחת למשתנה h.

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

עכשיו על מה טייפסקריפט התלונן?

  1. הקו האדום הראשון מתחת ל headers צויר שם בגלל ש querySelector עלולה להחזיר null. אם היא החזירה null (למשל כי האלמנט לא קיים בדף) אז הפקודה שכתבתי תזרוק Exception ותרסק את התוכנית.

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

  3. הקו האדום השלישי הוא על ה h והוא הכי עדין כאן. טייפסקריפט רוצה להגיד שאפילו אם הוא טעה עד עכשיו, ואפילו אם באמת יש forEach על משהו שחוזר מ querySelectorAll, עכשיו הוא תקוע כי הוא לא יודע מה הטיפוס של כל אחד מהאלמנטים שצריכים לעבור ל Callback Function. אם זה היה querySelectorAll שהוא מכיר הוא היה יודע מה צריך לעבור שם, אבל בגלל שזו פונקציה אחרת שאתם טוענים שמחזירה מערך, אתם תצטרכו לתת לטייפסקריפט יותר מידע כדי לבדוק את המשך הקוד.

3. עזרה בשינוי הקוד

טייפסקריפט יכול לעזור לנו להזיז קוד ממקום למקום בעזרת כמה פונקציות נחמדות של VS Code:

  1. כפתור Extract To Function יודע לקחת כמה שורות קוד ולהוציא אותן לפונקציה נפרדת, כאשר כל המשתנים הפנימיים הופכים להיות פרמטרים של הפונקציה.

  2. כפתור Rename Symbol יודע לשנות שם של משתנה או פונקציה בכל המקומות בהם שם זה מופיע.

  3. כפתור Show Call Hierarchy מראה את כל המקומות שקוראים לפונקציה מסוימת, כדי שיהיה לכם קל לשנות את כולם (למשל כדי להוסיף פרמטר לפונקציה).

נראה את שלושת הדוגמאות. תחילה סמנו בקוד את השורות:

const headers = document.querySelectorAll('h1,h2,h3,h4,h5,h6');
headers.forEach(h => {
  console.log(h.textContent);
});

ולחצו כפתור ימני. בחרו Extract To Function In Module Scope ואז הקלידו שם עבור הפונקציה. אני אבחר בשם main. התוצאה היא פונקציה חדשה בשם main שנוצרה בסוף הקובץ:

function main() {
  const headers = document.querySelectorAll('h1,h2,h3,h4,h5,h6');
  headers.forEach(h => {
    console.log(h.textContent);
  });
}

שלב שני - נסמן את שלושת השורות התחתונות של הפונקציה (כלומר בלי השורה שמגדירה את המשתנה headers) ונלחץ שוב על הכפתור הימני ושוב נבחר Extract To Function In Module Scope. הפעם אני בוחר בשם clearHeadersText ואני יכול לראות שהפונקציה מקבלת כקלט משתנה headers שאמור להיות מסוג NodeListOf. ככה היא נראית:

function clearHeadersText(headers: NodeListOf<Element>) {
  headers.forEach(h => {
    console.log(h.textContent);
  });
}

מבחינת VS Code ההבדל בין שני ה Refactorings היה שבפעם הראשונה הקוד התיחס למשתנה גלובאלי, ולכן אם הייתי מוציא רק את שלושת השורות האחרונות לפונקציה היא היתה ממשיכה להתיחס לאותו משתנה גלובאלי headers. בפעם השניה המשתנה headers היה משתנה פנימי של פונקציה main, ולכן בשביל להוציא החוצה את שלושת השורות לפונקציה חדשה אותה פונקציה חדשה חייבת לקבל את הערך של headers.

נמשיך ל h בתוך הלולאה:

  headers.forEach(h => {
    console.log(h.textContent);
  });

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

ועם כל הכיף ביצירת פונקציות בקלות, כפתור אחד שאני אפילו יותר אוהב הוא Show Call Hierarchy. נוסיף קריאה ל twice לתוך אחת הפונקציות:

function clearHeadersText(headers: NodeListOf<Element>) {
  headers.forEach(header => {
    console.log(header.textContent);
  });
  alert(twice(5));
}

ועכשיו נכנס לקובץ utils.ts, כפתור ימני על הפונקציה twice ולחיצה על Show Call Hierarchy. בתגובה תקבלו את רשימת המקומות שהפונקציה הזאת נקראת בהם, וככה קל לזהות מי משתמש בפונקציה או להוסיף או להוריד פרמטר.

4. עזרה בכתיבת הקוד

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

נעדכן את הפונקציה clearHeadersText לקוד הבא:

function clearHeadersText(headers: NodeListOf<HTMLHeadElement>) {
  headers.forEach(header => {
    console.log(header.textContent);
  });
  alert(twice(5));
}

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

header.addEventListener('

פה טייפסקריפט כבר יודע הרבה על מה שקורה מסביב: הוא יודע שיש לי ביד אלמנט מסוג HTMLHeadElement, והוא יודע איזה אירועים מותר לכתוב על אלמנט כזה. בידע הזה VS Code יכול להשתמש ובאמת רשימת ההשלמות שאני מקבל כוללת את כל האירועים האפשריים על אלמנטי head.

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

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

npm install --save axios

בחזרה לקוד ונכתוב פשוט axios ו Enter ואוטומטית VS Code מוסיף את ה import המתאים. אחרי המילה axios כותבים נקודה ואוטומטית אנחנו רואים רשימה של כל השדות שיש על אוביקט axios. אני משלים ל get ושוב מקבל את רשימת הפרמטרים, כולל רשימת המפתחות באוביקט הקונפיגורציה ש get מצפה לקבל.

5. סיכום

העבודה עם טייפסקריפט עוזרת לנו לכתוב JavaScript ב-3 אופנים:

  1. כלי הפיתוח יכולים לזהות טעויות של טיפוסים לא נכונים ולהתריע עליהן.

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

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

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