לקבל Types בJavaScript בלי לעבור לTypeScript

Cover image

למה צריך Types בכלל?

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

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

למשל, גם אם לא צריך Types בשביל לזכור שבתוך user יש user.name וuser.password, לפעמים יש מקרים שבהם יש אובייקט מורכב מאוד, למשל במקרים שבהם מושכים הרבה נתונים מNoSQL, או מקבלים תשובה בREST API. במקרים כאלה, לזכור את מבנה האובייקט, לטייל בו ולהוציא ממנו נתונים זה כבר ממש קשה.

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

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

אז למה לא TypeScript

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

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

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

Types without TypeScript?

אז כן, זה אפשרי!

בVSCode אפשר להגדיר Types בתוך הערות מיוחדות, הערות JSDoc שמיועדות לתיעוד. בסופו של דבר, TypeScript הוא הרחבה של JavaScript וכל JS יכול להיחשב כTS, אז VSCode ישתמש בכלי הTS שלו כדי להבין את הTypes ולתת לנו הצעות והערות.

זה נראה ככה:

/**
 * @param {number} a
 * @param {number} b
 * @returns {number}
 */
function add(a, b) {
  return a + b;
}

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

Syntax

אז מכיוון שהTypes לא משולבים בקוד, יש שילוב של תיעוד עם TypeScript. וזה מאוד פשוט בסה"כ.

@what {TypeScript} [who]

כלומר, קודם כל תבוא הגדרה של JSDoc לגבי מה אנחנו מגדירים, למשל param או return (ונראה עוד בהמשך). לאחר מכן יבוא הTypeScript עצמו, בתוך סוגריים מסולסלים, ובסוף, תלוי ב"מה", יכול לבוא עוד משתנה שמכוון למקום הנכון.

מה

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

/**
 * @typedef {{name: string, password: number}} User
 */

הtypedef משמש אותנו להגדרה של Type חדש, והוא לא חייב להיות צמוד לפונקציה או לקוד אחד. אחרי שהגדרנו אותו, נוכל להשתמש בו בהמשך כדי להצהיר על מערך של Users בקלות, למשל.

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

/**
 * @type User
 */
let user;

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

Type Inference

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

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

אם ניקח את הדוגמא הראשונה:

/**
 * @param {number} a
 * @param {number} b
 * @returns {number}
 */
function add(a, b) {
  return a + b;
}

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

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

Import

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

אם הגדרתם אותו בעזרת typedef, תוכלו לייבא אותו עם import:

/**
 * @typedef {import('./types').User} User
 */

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

ts-check

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

כדי להציג שגיאות של Types, יש להוסיף בראש הקובץ // @ts-check. האמת היא שניתן גם להגדיר את זה ברמת הפרויקט או בהגדרות של VSCode, אבל בגלל שזה לא ממש עבד לי, אני לא רוצה לתת פה דברים שאני לא בטוח בהם, אז פשוט תסתכלו בתיעוד ותנסו לראות אם אתם מצליחים.

סיכום

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

צריכים איזה Type פה ושם? תכניסו אותו בהערה. מרגישים שזה מתאים לכם, עוזר לכם, שווה את המחיר- תעברו לTypeScript.

והכי חשוב, תשקיעו את הזמן בקוד ולא בהגדרה של הTypes שלו!

הערות? החלטתם להתחיל להשתמש? שתפו אותי :-)