לדלג לתוכן

5.7 טיפוסי שירות ודפוסים מתקדמים הרצאה

טיפוסי שירות ודפוסים מתקדמים - utility types and advanced patterns

טייפסקריפט מגיעה עם אוסף של טיפוסים מובנים - utility types - שעוזרים לשנות ולמפות טיפוסים קיימים. בשיעור הזה נלמד את הטיפוסים האלה, ואת הדפוסים המתקדמים שבבסיסם.

Partial - הפיכת כל השדות לאופציונליים

Partial<T> הופך את כל השדות של טיפוס לאופציונליים:

interface User {
    name: string;
    age: number;
    email: string;
}

type PartialUser = Partial<User>;
// { name?: string; age?: number; email?: string }

// very useful for update functions
function updateUser(user: User, updates: Partial<User>): User {
    return { ...user, ...updates };
}

let user: User = { name: "Alice", age: 30, email: "a@b.com" };
let updated = updateUser(user, { age: 31 }); // only update age

Required - הפיכת כל השדות לחובה

Required<T> הוא ההפך של Partial - הופך את כל השדות לחובה:

interface Config {
    theme?: string;
    language?: string;
    debug?: boolean;
}

type FullConfig = Required<Config>;
// { theme: string; language: string; debug: boolean }

function initApp(config: FullConfig): void {
    // all fields guaranteed to exist
    console.log(`Theme: ${config.theme}, Debug: ${config.debug}`);
}

Readonly - הפיכת כל השדות לקריאה בלבד

Readonly<T> מונע שינוי של כל השדות:

interface State {
    count: number;
    items: string[];
}

type FrozenState = Readonly<State>;

let state: FrozenState = { count: 0, items: [] };
state.count = 1;    // ERROR: Cannot assign to 'count' because it is a read-only property
state.items = [];   // ERROR: Cannot assign to 'items'
state.items.push("a"); // OK! Readonly is shallow - items array itself is mutable

שימו לב: Readonly הוא שטחי. כדי ש-readonly יהיה עמוק, צריך פתרון מותאם:

type DeepReadonly<T> = {
    readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

Pick - בחירת שדות ספציפיים

Pick<T, K> יוצר טיפוס חדש עם רק השדות שבחרנו:

interface User {
    id: number;
    name: string;
    email: string;
    password: string;
    createdAt: Date;
}

type PublicUser = Pick<User, "id" | "name" | "email">;
// { id: number; name: string; email: string }

function getPublicProfile(user: User): PublicUser {
    return {
        id: user.id,
        name: user.name,
        email: user.email
    };
}

Omit - הסרת שדות ספציפיים

Omit<T, K> הוא ההפך של Pick - יוצר טיפוס בלי השדות שציינו:

type UserWithoutPassword = Omit<User, "password">;
// { id: number; name: string; email: string; createdAt: Date }

type CreateUserInput = Omit<User, "id" | "createdAt">;
// { name: string; email: string; password: string }

Omit שימושי מאוד כשרוצים טיפוס "כמו X אבל בלי Y" - למשל, טיפוס ליצירת רשומה (בלי id ותאריכים שנוצרים אוטומטית).

Record - אובייקט עם מפתחות וערכים מוגדרים

Record<K, V> יוצר טיפוס אובייקט שהמפתחות שלו הם מסוג K והערכים מסוג V:

// basic dictionary
type StringMap = Record<string, string>;

let env: StringMap = {
    NODE_ENV: "development",
    PORT: "3000"
};

// record with literal key types
type StatusMessages = Record<"success" | "error" | "loading", string>;

let messages: StatusMessages = {
    success: "Operation completed!",
    error: "Something went wrong",
    loading: "Please wait..."
};
// all three keys are required!

// practical example - feature flags
type FeatureFlags = Record<string, boolean>;

let features: FeatureFlags = {
    darkMode: true,
    betaFeatures: false,
    analytics: true
};

ReturnType - טיפוס ההחזרה של פונקציה

ReturnType<T> מחלץ את טיפוס ההחזרה של פונקציה:

function createUser(name: string, age: number) {
    return {
        id: Math.random(),
        name,
        age,
        createdAt: new Date()
    };
}

type User = ReturnType<typeof createUser>;
// { id: number; name: string; age: number; createdAt: Date }

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

Parameters - טיפוסי הפרמטרים של פונקציה

Parameters<T> מחלץ את טיפוסי הפרמטרים כטאפל:

function fetchData(url: string, options: { method: string; body?: string }) {
    // ...
}

type FetchParams = Parameters<typeof fetchData>;
// [url: string, options: { method: string; body?: string }]

// useful for creating wrapper functions
function loggedFetch(...args: Parameters<typeof fetchData>) {
    console.log(`Fetching: ${args[0]}`);
    return fetchData(...args);
}

Exclude ו-Extract - סינון union types

Exclude<T, U> מסיר טיפוסים מ-union:

type AllTypes = string | number | boolean | null | undefined;

type NonNullable2 = Exclude<AllTypes, null | undefined>;
// string | number | boolean

type StringsOnly = Extract<AllTypes, string>;
// string

Extract<T, U> משאיר רק טיפוסים שתואמים:

type Events = "click" | "scroll" | "keypress" | "mousemove" | "mouseenter" | "mouseleave";

type MouseEvents = Extract<Events, `mouse${string}`>;
// "mousemove" | "mouseenter" | "mouseleave"

NonNullable - הסרת null ו-undefined

NonNullable<T> מסיר null ו-undefined מ-union:

type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// string

טיפוסים ממופים - mapped types

mapped types הם הבסיס של רוב ה-utility types. הם "עוברים" על כל המפתחות של טיפוס ויוצרים טיפוס חדש:

// this is basically how Partial is implemented:
type MyPartial<T> = {
    [K in keyof T]?: T[K];
};

// and Readonly:
type MyReadonly<T> = {
    readonly [K in keyof T]: T[K];
};

// and Required:
type MyRequired<T> = {
    [K in keyof T]-?: T[K]; // -? removes the optional modifier
};

mapped types מותאמים

// make all properties nullable
type Nullable<T> = {
    [K in keyof T]: T[K] | null;
};

interface User {
    name: string;
    age: number;
    email: string;
}

type NullableUser = Nullable<User>;
// { name: string | null; age: number | null; email: string | null }

// make all properties into getters
type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number; getEmail: () => string }

טיפוסים מותנים - conditional types

conditional types עובדים כמו if/else על טיפוסים:

type IsString<T> = T extends string ? true : false;

type A = IsString<string>;  // true
type B = IsString<number>;  // false
type C = IsString<"hello">; // true (literal extends string)

שימוש מעשי

// extract array element type
type ElementType<T> = T extends (infer U)[] ? U : never;

type A = ElementType<string[]>;   // string
type B = ElementType<number[]>;   // number
type C = ElementType<string>;     // never (not an array)

// flatten one level
type Flatten<T> = T extends (infer U)[] ? U : T;

type D = Flatten<string[]>;  // string
type E = Flatten<string>;    // string (already flat)

infer - חילוץ טיפוס מתוך טיפוס

infer מאפשר "ללכוד" חלק מטיפוס בתוך conditional type:

// extract return type (this is how ReturnType works internally)
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type A = MyReturnType<() => string>;           // string
type B = MyReturnType<(x: number) => boolean>; // boolean

// extract promise value
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type C = UnwrapPromise<Promise<string>>;  // string
type D = UnwrapPromise<Promise<number>>;  // number
type E = UnwrapPromise<string>;           // string (not a promise, return as-is)

דוגמה מעשית - חילוץ פרמטר ראשון

type FirstParam<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;

type A = FirstParam<(name: string, age: number) => void>; // string
type B = FirstParam<(x: number) => void>;                  // number
type C = FirstParam<() => void>;                           // never (no params)

טיפוסי תבנית ליטרלית - template literal types

טייפסקריפט מאפשרת ליצור טיפוסי מחרוזת ליטרליים עם תבניות:

type Color = "red" | "green" | "blue";
type Size = "small" | "medium" | "large";

type ClassName = `${Size}-${Color}`;
// "small-red" | "small-green" | "small-blue" |
// "medium-red" | "medium-green" | "medium-blue" |
// "large-red" | "large-green" | "large-blue"

TS יוצרת את כל הקומבינציות אוטומטית!

שימוש מעשי - CSS properties

type CSSProperty = "margin" | "padding";
type Direction = "top" | "right" | "bottom" | "left";

type DirectionalCSS = `${CSSProperty}-${Direction}`;
// "margin-top" | "margin-right" | ... | "padding-left"

שימוש עם אירועים

type EventName = "click" | "focus" | "blur";
type HandlerName = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"

טייפסקריפט מספקת utility types למחרוזות: Capitalize, Uncapitalize, Uppercase, Lowercase.

שילוב הכל - דוגמה מתקדמת

בואו נבנה טיפוס FormState שמייצר אוטומטית מצב לטופס:

interface UserForm {
    name: string;
    email: string;
    age: number;
}

// generate form state from form fields
type FormState<T> = {
    values: T;
    errors: Partial<Record<keyof T, string>>;
    touched: Partial<Record<keyof T, boolean>>;
    isValid: boolean;
    isSubmitting: boolean;
};

type UserFormState = FormState<UserForm>;

let formState: UserFormState = {
    values: { name: "", email: "", age: 0 },
    errors: { email: "Invalid email" },
    touched: { name: true, email: true },
    isValid: false,
    isSubmitting: false
};

דוגמה - API client מוטייפ

interface ApiEndpoints {
    "/users": {
        GET: { response: User[]; params: { page?: number } };
        POST: { response: User; body: Omit<User, "id"> };
    };
    "/users/:id": {
        GET: { response: User; params: { id: string } };
        PUT: { response: User; body: Partial<User> };
        DELETE: { response: void; params: { id: string } };
    };
}

type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

type EndpointConfig<
    Path extends keyof ApiEndpoints,
    Method extends keyof ApiEndpoints[Path]
> = ApiEndpoints[Path][Method];

סיכום

  • Partial - הופך הכל לאופציונלי. שימושי לפונקציות update
  • Required - הופך הכל לחובה
  • Readonly - מונע שינויים (שטחי)
  • Pick - בוחר שדות ספציפיים
  • Omit - מסיר שדות ספציפיים
  • Record - יוצר אובייקט עם מפתחות וערכים מוגדרים
  • ReturnType - מחלץ טיפוס החזרה של פונקציה
  • Parameters - מחלץ טיפוסי פרמטרים של פונקציה
  • Exclude/Extract - מסנן union types
  • NonNullable - מסיר null ו-undefined
  • mapped types הם הבסיס - עוברים על מפתחות ויוצרים טיפוס חדש
  • conditional types עובדים כמו if/else על טיפוסים
  • infer מחלץ חלקים מטיפוס
  • template literal types יוצרים מחרוזות ליטרליות דינמיות