לדלג לתוכן

5.2 טיפוסים בסיסיים פתרון

פתרון - טיפוסים בסיסיים

פתרון תרגיל 1

let city: string = "Tel Aviv";
let population: number = 460613;
let isCapital: boolean = false;
let coordinates: number[] = [32.0853, 34.7818];
let info: { name: string; country: string; founded: number } = {
    name: "Tel Aviv",
    country: "Israel",
    founded: 1909
};
let mayor: null = null;
let nickname: undefined = undefined;

באילו מקרים ה-annotation הכרחי:
- city, population, isCapital, coordinates, info - TS מסיקה נכון לבד, אז ה-annotation לא הכרחי
- mayor ו-nickname - TS תסיק null ו-undefined בהתאמה, מה שאומר שהמשתנה יכול להכיל רק את הערך הזה. אם רוצים שהם יוכלו להחזיק ערכים אחרים בעתיד, צריך annotation מפורש כמו let mayor: string | null = null

פתרון תרגיל 2

א. טאפל coordinate:

let coordinate: [number, number] = [32.0853, 34.7818];

ב. טאפל httpResponse:

let httpResponse: [number, string, boolean] = [200, "OK", true];

ג. פונקציה parseEntry:

function parseEntry(entry: string): [string, number] {
    let parts = entry.split(":");
    let name = parts[0];
    let age = parseInt(parts[1]);
    return [name, age];
}

let [name, age] = parseEntry("Alice:30");
console.log(name); // "Alice"
console.log(age);  // 30

ד. push לטאפל:

let pair: [string, number] = ["Alice", 30];
pair.push("extra"); // no compile error! this is a known quirk in TypeScript
console.log(pair);  // ["Alice", 30, "extra"]

זו נקודת חולשה ידועה בטייפסקריפט - push לא נבדק בטאפלים. הסיבה היא שהטיפוס של push מוגדר לקבל string | number (union של טיפוסי האיברים), וזה מתקמפל. אם רוצים טאפל באמת קבוע, אפשר להשתמש ב-readonly:

let pair: readonly [string, number] = ["Alice", 30];
pair.push("extra"); // ERROR: Property 'push' does not exist on readonly tuple

פתרון תרגיל 3

א. enum LogLevel:

enum LogLevel {
    Debug,    // 0
    Info,     // 1
    Warning,  // 2
    Error,    // 3
    Critical  // 4
}

ב. string enum HttpMethod:

enum HttpMethod {
    Get = "GET",
    Post = "POST",
    Put = "PUT",
    Delete = "DELETE"
}

ג. גרסת as const:

// LogLevel as const
const LogLevel = {
    Debug: 0,
    Info: 1,
    Warning: 2,
    Error: 3,
    Critical: 4
} as const;

type LogLevel = typeof LogLevel[keyof typeof LogLevel];
// type: 0 | 1 | 2 | 3 | 4

// HttpMethod as const
const HttpMethod = {
    Get: "GET",
    Post: "POST",
    Put: "PUT",
    Delete: "DELETE"
} as const;

type HttpMethod = typeof HttpMethod[keyof typeof HttpMethod];
// type: "GET" | "POST" | "PUT" | "DELETE"

ד. getMethodColor:

function getMethodColor(method: HttpMethod): string {
    switch (method) {
        case HttpMethod.Get: return "green";
        case HttpMethod.Post: return "blue";
        case HttpMethod.Put: return "orange";
        case HttpMethod.Delete: return "red";
    }
}

פתרון תרגיל 4

function processValue(value: unknown): string {
    if (typeof value === "number") {
        return value.toFixed(2);
    }
    if (typeof value === "string") {
        return value.toUpperCase();
    }
    return String(value);
}

function getLength(value: unknown): number {
    if (typeof value === "string") {
        return value.length;
    }
    if (Array.isArray(value)) {
        return value.length;
    }
    return 0;
}

function callIfFunction(value: unknown): void {
    if (typeof value === "function") {
        value();
    }
}

ההבדל: עם any, הקוד מתקמפל אבל קורס ב-runtime אם הטיפוס לא מתאים (למשל processValue(true).toUpperCase() יקרוס). עם unknown, TS מכריחה אותנו לבדוק את הטיפוס לפני שימוש, ולטפל בכל המקרים.

פתרון תרגיל 5

א. findUser:

function findUser(users: string[], name: string): string | null {
    let found = users.find(u => u === name);
    return found ?? null;
}

ב. formatUser:

function formatUser(user: { name: string; email?: string }): string {
    if (user.email) {
        return `${user.name} (${user.email})`;
    }
    return `${user.name} (no email)`;
}

ג. safeParseInt:

function safeParseInt(value: string | null | undefined): number | null {
    if (value === null || value === undefined) {
        return null;
    }
    let result = parseInt(value);
    if (isNaN(result)) {
        return null;
    }
    return result;
}

פתרון תרגיל 6

א. טיפוסים מוסקים:

const name = "Alice";           // "Alice" (literal type - const)
let name2 = "Alice";            // string (let - can be reassigned)
const age = 30;                 // 30 (literal type)
let age2 = 30;                  // number
const isAdmin = true;           // true (literal type)
const coords = [1, 2] as const; // readonly [1, 2]
let coords2 = [1, 2];           // number[]
const config = { debug: true, version: "1.0" };
// { debug: boolean; version: string } - properties are NOT literal even with const
const config2 = { debug: true, version: "1.0" } as const;
// { readonly debug: true; readonly version: "1.0" } - literal + readonly

ב. למה const arr = [1, 2, 3] הוא number[] ולא [1, 2, 3]:
כי const מונע השמה מחדש של המשתנה, אבל לא מונע שינוי התוכן של המערך. אפשר לעשות arr.push(4) או arr[0] = 99. לכן TS לא יכולה להניח שהתוכן קבוע, והיא מסיקה number[]. כדי לקבל טיפוס ליטרלי למערך, צריך as const.

ג. Season:

type Season = "spring" | "summer" | "autumn" | "winter";

function getTemperature(season: Season): number {
    switch (season) {
        case "spring": return 20;
        case "summer": return 35;
        case "autumn": return 22;
        case "winter": return 12;
    }
}

פתרון תרגיל 7

א. student:

let student: {
    name: string;
    age: number;
    grades: number[];
    address: { city: string; street: string };
    graduated?: boolean;
} = {
    name: "Alice",
    age: 22,
    grades: [95, 88, 72, 100],
    address: { city: "Tel Aviv", street: "Rothschild 1" }
};

ב. readonlyPoint:

let readonlyPoint: { readonly x: number; readonly y: number } = { x: 10, y: 20 };
readonlyPoint.x = 5; // ERROR: Cannot assign to 'x' because it is a read-only property

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

// the problem:
let student1: { name: string; age: number; grades: number[] } = { ... };
let student2: { name: string; age: number; grades: number[] } = { ... };
// duplicated type! if we add a field, we need to update both places

פתרון תרגיל 8

enum Priority {
    Low = "low",
    Medium = "medium",
    High = "high"
}

type Task = {
    title: string;
    priority: string;
    tags: string[];
    due_date: string | null;
    completed: boolean;
};

function createTask(
    title: string,
    priority: Priority,
    tags: string[],
    dueDate: string | null = null
): Task {
    return {
        title: title,
        priority: priority,
        tags: tags,
        due_date: dueDate,
        completed: false
    };
}

let tasks: Task[] = [];

let task1 = createTask("Learn TypeScript", Priority.High, ["coding", "study"]);
let task2 = createTask("Buy groceries", Priority.Low, ["personal"], "2024-12-31");

tasks.push(task1);
tasks.push(task2);

for (let task of tasks) {
    let status = task.completed ? "done" : "pending";
    console.log(`${task.title} - ${status}`);
}

הבדלים מפייתון:
- Optional[str] הפך ל-string | null
- ערך ברירת מחדל None הפך ל-null
- dict הפך לטיפוס מפורש Task עם כל השדות
- list[dict] הפך ל-Task[]
- priority.value בפייתון מחזיר את הערך של ה-enum, בטייפסקריפט הערך של string enum כבר הוא המחרוזת
- append הפך ל-push
- for task in tasks הפך ל-for (let task of tasks)
- f-string הפך ל-template literal