שיטות עבודה מומלצות בתכנות TypeScript

שיטות עבודה מומלצות בתכנות TypeScript

מאמר זה מסביר שיטות עבודה מומלצות בתכנות TypeScript.

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

YouTube Video

שיטות עבודה מומלצות בתכנות TypeScript

היתרון הגדול ביותר של TypeScript הוא 'מניעת באגים באמצעות סוגים והבהרת מטרת הקוד'.

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

הימנעו מהשימוש ב-any ותמיד תנו לסוגים הגדרות משמעותיות

ראשית, בואו נבחן את הנקודה של 'הימנעות מ־any ונתינת טיפוסים בעלי משמעות.'.

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

1// Bad
2function parse(data: any) {
3  return data.value;
4}

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

1// Good
2type ParsedData = {
3  value: string;
4};
5
6function parse(data: ParsedData): string {
7  return data.value;
8}

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

תמיד הגדר מפורשות את מבנה האובייקטים באמצעות type או interface.

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

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

1// Bad
2function createUser(user: { name: string; age: number }) {
3  console.log(user.name);
4}

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

1// Good
2type User = {
3  name: string;
4  age: number;
5};
6
7function createUser(user: User): void {
8  console.log(user.name);
9}

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

השתמש בסוגי איחוד (union) כדי לייצג במדויק את כל המצבים האפשריים.

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

1// Bad
2function setStatus(status: string) {
3  console.log(status);
4}

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

1// Good
2type Status = "idle" | "loading" | "success" | "error";
3
4function setStatus(status: Status): void {
5  console.log(status);
6}

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

טפל ב-null ו-undefined במפורש.

הלאה, בואו נבחן את הנקודה של 'טיפול מפורש ב־null ו־undefined.'.

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

1type User = {
2  name: string;
3  email?: string;
4};

email עלול לא להתקיים, לכן יש לטפל בו בהנחה זו.

1function printEmail(user: User): void {
2  if (user.email) {
3    console.log(user.email);
4  }
5}

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

השתמש בהצהרות סוג (as) רק כמוצא אחרון.

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

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

1// Bad
2const value = input as string;

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

1// Good
2function isString(value: unknown): value is string {
3  return typeof value === "string";
4}
5
6if (isString(input)) {
7  console.log(input.toUpperCase());
8}

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

אל תסמוך יתר על המידה על Type Inference עבור סוגי ערך מוחזר.

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

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

1// Bad
2function sum(a: number, b: number) {
3  return a + b;
4}

הצהר את כוונתך בבירור גם בפונקציות קטנות.

1// Good
2function sum(a: number, b: number): number {
3  return a + b;
4}

כתיבת סוגי ערך מוחזר מגבירה את יציבות ה-API שלך.

טפל בקלטים בצורה בטוחה באמצעות unknown.

הלאה, בואו נבחן את הנקודה של 'קבלת קלט חיצוני בצורה בטוחה באמצעות unknown.'.

לקלט חיצוני כמו APIs, JSON או קלט משתמש, השתמש ב־unknown במקום any. בכך אתה מוודא שכל הערכים עוברים אימות, ושומר על בטיחות הסוגים.

1// Bad
2function handleResponse(data: any) {
3  console.log(data.id);
4}

כך ניתן לאמת סוגים בעת שימוש ב-unknown.

 1// Good
 2function handleResponse(data: unknown): void {
 3  if (
 4    typeof data === "object" &&
 5    data !== null &&
 6    "id" in data
 7  ) {
 8    console.log((data as { id: number }).id);
 9  }
10}

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

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

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

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

 1type Id = number;
 2
 3type UserProfile = {
 4  id: Id;
 5  name: string;
 6};
 7
 8type UserWithStatus = UserProfile & {
 9  status: "active" | "inactive";
10};

התייחסות לסוגים כאל רכיבים עוזרת לארגן את העיצוב שלך.

type ו-interface

יתרונות interface

type ו-interface יכולים שניהם להגדיר סוגים, אך השימושים והמאפיינים שלהם שונים. על ידי שימוש בהם לתפקידים המתאימים, הכוונה של הגדרות הסוג שלך נהיית ברורה יותר.

 1// Bad
 2type User = {
 3  id: number;
 4  name: string;
 5};
 6
 7type AdminUser = {
 8  id: number;
 9  name: string;
10  role: "admin";
11};

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

1// Good
2interface User {
3  id: number;
4  name: string;
5}
6
7interface AdminUser extends User {
8  role: "admin";
9}

interface אידיאלי לעיצובים שמערבים הרחבה (extends) ומתאים ביותר להביע את ה'צורה' של אובייקטים.

יתרונות type

מצד שני, type הוא ביטויי יותר ומתאים לטיפול בסוגי איחוד וחיתוך (union/intersection).

1// Good
2type Status = "idle" | "loading" | "success" | "error";
3
4type ApiResponse<T> =
5  | { status: "success"; data: T }
6  | { status: "error"; message: string };

type מתאים היטב להביע מצבים, אפשרויות ושילובים.

הנחיות לבחירה בין type ל-interface

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

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

הפוך את הסוגים שלך לתיעוד.

ולבסוף, בואו נבחן את הנקודה של 'כתיבת טיפוסים כתיעוד.'.

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

1type ApiError = {
2  code: number;
3  message: string;
4  retryable: boolean;
5};

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

סיכום

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

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

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

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

YouTube Video