לדלג לתוכן

5.2 טיפוסים בסיסיים הרצאה

טיפוסים בסיסיים - basic types

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

טיפוסים פרימיטיביים - primitives

מחרוזת - string

let firstName: string = "Alice";
let greeting: string = `Hello, ${firstName}!`; // template literals work too
let empty: string = "";

מספר - number

בטייפסקריפט (כמו בג׳אווהסקריפט) יש טיפוס מספרי אחד בלבד - number. הוא מכסה שלמים ועשרוניים:

let age: number = 30;
let price: number = 9.99;
let negative: number = -10;
let hex: number = 0xff;
let binary: number = 0b1010;

זה שונה מפייתון שם יש int ו-float בנפרד. בטייפסקריפט הכל number.

בוליאני - boolean

let isActive: boolean = true;
let isLoggedIn: boolean = false;

שימו לב: boolean ולא bool כמו בפייתון.

מערכים - arrays

שתי דרכים שקולות להגדיר מערך:

// syntax 1: type[]
let numbers: number[] = [1, 2, 3];
let names: string[] = ["Alice", "Bob"];

// syntax 2: Array<type> (generic syntax - we'll learn more about this later)
let scores: Array<number> = [100, 95, 88];

שני התחבירים זהים לגמרי. הקונבנציה הנפוצה היא type[] - קצר וקריא.

מערך ריק צריך טיפוס מפורש, אחרת TS תסיק any[]:

let items: string[] = []; // good - TS knows this is string[]
let stuff = [];           // bad - TS infers any[]

השוואה לפייתון

# Python
numbers: list[int] = [1, 2, 3]
names: list[str] = ["Alice", "Bob"]
// TypeScript
let numbers: number[] = [1, 2, 3];
let names: string[] = ["Alice", "Bob"];

טאפל - tuple

טאפל הוא מערך עם אורך קבוע וטיפוס מוגדר לכל מיקום:

// a tuple with exactly 2 elements: string and number
let person: [string, number] = ["Alice", 30];

// access by index
console.log(person[0]); // "Alice" - TS knows this is string
console.log(person[1]); // 30 - TS knows this is number

// errors
person[0] = 42;         // ERROR: Type 'number' is not assignable to type 'string'
person[2];              // ERROR: Tuple type has no element at index '2'

זה דומה לטאפל של פייתון, אבל בפייתון טאפלים הם immutable ובטייפסקריפט הם mutable (אפשר לשנות ערכים, רק לא את הטיפוסים).

// common use case: function returning multiple values
function getNameAndAge(): [string, number] {
    return ["Alice", 30];
}

let [name, age] = getNameAndAge(); // destructuring works!

אינום - enum

enum מגדיר קבוצה של קבועים בעלי שם:

enum Direction {
    Up,
    Down,
    Left,
    Right
}

let dir: Direction = Direction.Up;
console.log(dir); // 0 - enums are numeric by default

כברירת מחדל, הערכים הם מספרים שמתחילים מ-0. אפשר לשנות:

enum HttpStatus {
    OK = 200,
    NotFound = 404,
    InternalError = 500
}

console.log(HttpStatus.OK); // 200

אינום מחרוזתי - string enum

enum Color {
    Red = "RED",
    Green = "GREEN",
    Blue = "BLUE"
}

let favoriteColor: Color = Color.Red;
console.log(favoriteColor); // "RED"

string enums הם נפוצים יותר בפרקטיקה כי קל יותר לדבג אותם (רואים "RED" במקום 0).

השוואה לפייתון

# Python
from enum import Enum

class Color(Enum):
    RED = "RED"
    GREEN = "GREEN"
    BLUE = "BLUE"
// TypeScript
enum Color {
    Red = "RED",
    Green = "GREEN",
    Blue = "BLUE"
}

הערה: בפרויקטים מודרניים הרבה מפתחים מעדיפים as const על פני enum - נראה את זה בהמשך.

הטיפוסים המיוחדים

any - ביטול בדיקת טיפוסים

any מכבה את בדיקת הטיפוסים לחלוטין. כל דבר מותר:

let value: any = 42;
value = "hello";   // ok
value = true;      // ok
value.foo.bar;     // ok - no checking at all!
value();           // ok - TS doesn't care

אל תשתמשו ב-any אלא אם אין ברירה. הוא מבטל את כל היתרונות של טייפסקריפט. אם strict: true מופעל, TS תתריע כש-any מוסק בשקט (noImplicitAny).

unknown - הטיפוס הבטוח ל"לא יודע"

unknown הוא כמו any, אבל בטוח - חייבים לבדוק את הטיפוס לפני שמשתמשים בו:

let value: unknown = 42;
value = "hello";    // ok - can assign anything

// but can't USE it without checking first:
value.toUpperCase();        // ERROR: 'value' is of type 'unknown'
(value as string).toUpperCase(); // ok - type assertion
if (typeof value === "string") {
    value.toUpperCase();    // ok - TS narrows to string
}

כלל: כשלא יודעים מה הטיפוס, השתמשו ב-unknown ולא ב-any.

never - טיפוס שלעולם לא קורה

never מייצג ערך שלא יכול להתקיים. שימושים עיקריים:

// function that never returns (throws or infinite loop)
function throwError(message: string): never {
    throw new Error(message);
}

// exhaustive checking - we'll see this more in lesson 5.4
function handleShape(shape: "circle" | "square"): number {
    switch (shape) {
        case "circle": return 3.14;
        case "square": return 4;
        default:
            const exhaustiveCheck: never = shape; // ERROR if we miss a case
            return exhaustiveCheck;
    }
}

void - פונקציה שלא מחזירה ערך

function logMessage(message: string): void {
    console.log(message);
    // no return statement
}

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

null ו-undefined

בטייפסקריפט עם strictNullChecks (שמופעל כחלק מ-strict: true), null ו-undefined הם טיפוסים נפרדים:

let a: string = null;      // ERROR: Type 'null' is not assignable to type 'string'
let b: string = undefined;  // ERROR: Type 'undefined' is not assignable to type 'string'

// if a variable CAN be null, you must say so explicitly:
let c: string | null = null;       // ok
let d: string | undefined = undefined; // ok

סימן השאלה בפרמטר אופציונלי הוא קיצור ל-| undefined:

function greet(name?: string): string {
    // name is string | undefined here
    return `Hello, ${name ?? "stranger"}!`;
}

הסקת טיפוסים - type inference

טייפסקריפט מסיקה טיפוסים אוטומטית בהרבה מקרים. בואו נראה את הכללים:

// TS infers from the initial value
let x = 42;           // number
let y = "hello";      // string
let z = true;         // boolean
let arr = [1, 2, 3];  // number[]

// TS infers function return type from the return statement
function add(a: number, b: number) {
    return a + b;  // return type inferred as number
}

// TS infers from context
let names = ["Alice", "Bob", "Charlie"];
names.map(name => name.toUpperCase()); // TS knows 'name' is string

מתי TS לא יכולה להסיק

// function parameters - must annotate
function greet(name) { } // ERROR with strict: Parameter 'name' implicitly has 'any' type

// empty arrays
let items = []; // any[] - must annotate: let items: string[] = [];

// variable without initial value
let result;     // any - must annotate: let result: number;

הכלל: אם TS מסיקה נכון, אל תכתבו את הטיפוס. אם לא - כתבו.

טיפוסים ליטרליים - literal types

טיפוס ליטרלי הוא טיפוס שמייצג ערך ספציפי אחד בלבד:

let direction: "up" | "down" | "left" | "right" = "up";
direction = "up";     // ok
direction = "down";   // ok
direction = "sideways"; // ERROR: Type '"sideways"' is not assignable

let httpStatus: 200 | 404 | 500 = 200;
httpStatus = 200; // ok
httpStatus = 201; // ERROR

טיפוסים ליטרליים חזקים מאוד בשילוב עם union types. נרחיב עליהם בשיעור 5.5.

הבדל בין let ל-const בהסקה

let x = "hello";    // type: string (can be reassigned to any string)
const y = "hello";  // type: "hello" (literal type - can never change)

let n = 42;          // type: number
const m = 42;        // type: 42

כש-TS רואה const, היא יודעת שהערך לעולם לא ישתנה ומסיקה טיפוס ליטרלי. עם let, היא מסיקה טיפוס רחב יותר כי הערך יכול להשתנות.

as const

as const הופך ערך לקבוע לחלוטין - כל הערכים הופכים לליטרלים ולקריאה בלבד:

// without as const
let colors = ["red", "green", "blue"]; // string[]

// with as const
let colorsConst = ["red", "green", "blue"] as const;
// type: readonly ["red", "green", "blue"]

colorsConst[0] = "yellow"; // ERROR: Cannot assign to '0' because it is read-only
colorsConst.push("purple"); // ERROR: Property 'push' does not exist on type 'readonly [...]'

as const שימושי במיוחד עם אובייקטים:

const config = {
    apiUrl: "https://api.example.com",
    timeout: 5000,
    retries: 3
} as const;

// type: { readonly apiUrl: "https://api.example.com"; readonly timeout: 5000; readonly retries: 3 }
config.apiUrl = "other"; // ERROR: Cannot assign to 'apiUrl' because it is read-only

as const כתחליף ל-enum

הרבה מפתחים מעדיפים as const על פני enum:

// instead of enum:
const Direction = {
    Up: "UP",
    Down: "DOWN",
    Left: "LEFT",
    Right: "RIGHT"
} as const;

type Direction = typeof Direction[keyof typeof Direction];
// type: "UP" | "DOWN" | "LEFT" | "RIGHT"

let dir: Direction = "UP"; // ok

היתרון: הקוד יותר צפוי, אין הפתעות של runtime behavior שיש ב-enum, ובקומפילציה ל-JS אין קוד מיותר.

אנוטציות על אובייקטים - object annotations

אפשר להגדיר את צורת האובייקט ישירות (inline):

let user: { name: string; age: number; isAdmin: boolean } = {
    name: "Alice",
    age: 30,
    isAdmin: false
};

שדות אופציונליים

let product: { name: string; price: number; description?: string } = {
    name: "Laptop",
    price: 999
    // description is optional - can be omitted
};

קריאה בלבד - readonly

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

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

השוואה מסכמת לפייתון

פייתון טייפסקריפט הערה
str string
int, float number טיפוס מספרי אחד בלבד
bool boolean
list[int] number[]
tuple[str, int] [string, number]
Enum enum או as const
Any any
None void / undefined
Optional[str] string \| undefined
Literal["a", "b"] "a" \| "b"

סיכום

  • הטיפוסים הבסיסיים: string, number, boolean
  • מערכים: type[] או Array<type>
  • טאפלים: [type1, type2] - מערך עם אורך וטיפוסים קבועים
  • enum מגדיר קבוצת קבועים, אבל as const הוא אלטרנטיבה מודרנית
  • any מכבה את בדיקת הטיפוסים - להימנע
  • unknown הוא ה-any הבטוח - חייבים לבדוק לפני שימוש
  • never מייצג ערך שלא קורה
  • void לפונקציות שלא מחזירות ערך
  • null ו-undefined הם טיפוסים נפרדים עם strictNullChecks
  • TS מסיקה טיפוסים אוטומטית - כתבו רק כשצריך
  • טיפוסים ליטרליים מגבילים לערך ספציפי: "up" | "down"
  • as const הופך ערכים לליטרלים readonly
  • אפשר לתאר צורת אובייקט inline, אבל לטיפוסים מורכבים עדיף interface/type