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 יוצרים מחרוזות ליטרליות דינמיות