5.7 טיפוסי שירות ודפוסים מתקדמים פתרון
פתרון - טיפוסי שירות ודפוסים מתקדמים¶
פתרון תרגיל 1¶
interface Product {
id: number;
name: string;
price: number;
description?: string;
category?: string;
inStock: boolean;
}
א. updateProduct:
function updateProduct(product: Product, updates: Partial<Product>): Product {
return { ...product, ...updates };
}
let product: Product = { id: 1, name: "Laptop", price: 999, inStock: true };
let updated = updateProduct(product, { price: 899, inStock: false });
ב. createProduct:
function createProduct(input: Required<Product>): Product {
return input;
}
let product = createProduct({
id: 1,
name: "Laptop",
price: 999,
description: "A great laptop",
category: "Electronics",
inStock: true
});
ג. freezeProduct:
function freezeProduct(product: Product): Readonly<Product> {
return product;
}
let frozen = freezeProduct(product);
frozen.price = 0; // ERROR: Cannot assign to 'price' because it is a read-only property
ד. ProductDraft:
type ProductDraft = Pick<Product, "name"> & Partial<Omit<Product, "name">>;
let draft: ProductDraft = { name: "New Product" }; // ok - only name is required
let draft2: ProductDraft = { name: "Phone", price: 499 }; // ok
פתרון תרגיל 2¶
interface Employee {
id: number;
firstName: string;
lastName: string;
email: string;
department: string;
salary: number;
hireDate: Date;
manager?: Employee;
}
א-ג. טיפוסים:
type EmployeeCard = Pick<Employee, "firstName" | "lastName" | "department">;
type EmployeePublicInfo = Omit<Employee, "salary" | "manager">;
type NewEmployeeInput = Omit<Employee, "id" | "hireDate">;
ד. toEmployeeCard:
function toEmployeeCard(employee: Employee): EmployeeCard {
return {
firstName: employee.firstName,
lastName: employee.lastName,
department: employee.department
};
}
ה. createEmployee:
let nextId = 1;
function createEmployee(input: NewEmployeeInput): Employee {
return {
...input,
id: nextId++,
hireDate: new Date()
};
}
let employee = createEmployee({
firstName: "Alice",
lastName: "Smith",
email: "alice@company.com",
department: "Engineering",
salary: 100000
});
פתרון תרגיל 3¶
א. GradeBook:
type GradeBook = Record<string, number[]>;
function getAverage(grades: GradeBook, student: string): number | null {
let studentGrades = grades[student];
if (!studentGrades || studentGrades.length === 0) {
return null;
}
let sum = studentGrades.reduce((total, grade) => total + grade, 0);
return sum / studentGrades.length;
}
let grades: GradeBook = {
Alice: [90, 85, 92],
Bob: [78, 82, 88]
};
console.log(getAverage(grades, "Alice")); // 89
console.log(getAverage(grades, "Charlie")); // null
ב. StatusColor:
type StatusColor = Record<"info" | "success" | "warning" | "error", string>;
let statusColors: StatusColor = {
info: "#3498db",
success: "#2ecc71",
warning: "#f39c12",
error: "#e74c3c"
};
// if you forget one, TS gives an error:
// let bad: StatusColor = { info: "blue", success: "green" };
// ERROR: Property 'warning' is missing
ג. TranslationMap:
type TranslationMap = Record<string, Record<string, string>>;
let translations: TranslationMap = {
en: {
greeting: "Hello",
farewell: "Goodbye",
thanks: "Thank you"
},
he: {
greeting: "שלום",
farewell: "להתראות",
thanks: "תודה"
}
};
function translate(lang: string, key: string): string {
return translations[lang]?.[key] ?? key;
}
console.log(translate("he", "greeting")); // "שלום"
console.log(translate("en", "thanks")); // "Thank you"
console.log(translate("en", "missing")); // "missing"
פתרון תרגיל 4¶
א. ReturnType ו-Parameters:
function createConfig(
theme: "light" | "dark",
language: string,
features: string[],
debug?: boolean
) {
return {
theme,
language,
features,
debug: debug ?? false,
version: "1.0.0",
createdAt: new Date()
};
}
type AppConfig = ReturnType<typeof createConfig>;
type ConfigParams = Parameters<typeof createConfig>;
ב. createDefaultConfig:
ג. wrapWithLogging:
function wrapWithLogging<T extends (...args: any[]) => any>(fn: T): T {
let wrapped = (...args: Parameters<T>): ReturnType<T> => {
console.log("Arguments:", args);
let result = fn(...args);
console.log("Result:", result);
return result;
};
return wrapped as T;
}
let loggedConfig = wrapWithLogging(createConfig);
let config = loggedConfig("dark", "he", ["auth"], true);
// logs: Arguments: ["dark", "he", ["auth"], true]
// logs: Result: { theme: "dark", ... }
פתרון תרגיל 5¶
א. 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 }
ב. Mutable:
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
interface FrozenPoint {
readonly x: number;
readonly y: number;
}
type MutablePoint = Mutable<FrozenPoint>;
// { x: number; y: number }
let point: MutablePoint = { x: 1, y: 2 };
point.x = 10; // ok - no longer readonly
ג. 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 }
הסבר: string & K מוודא ש-K הוא מחרוזת (keyof יכול להחזיר גם number ו-symbol). as עם template literal type משנה את שם המפתח.
פתרון תרגיל 6¶
א. IsArray:
type IsArray<T> = T extends any[] ? true : false;
type A = IsArray<string[]>; // true
type B = IsArray<number>; // false
type C = IsArray<[1, 2, 3]>; // true (tuple is also an array)
ב. UnwrapPromise (רקורסיבי):
type UnwrapPromise<T> = T extends Promise<infer U> ? UnwrapPromise<U> : T;
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<Promise<Promise<number>>>; // number
type C = UnwrapPromise<string>; // string
ג. FunctionInfo:
type FunctionInfo<T> = T extends (...args: infer P) => infer R
? {
params: P;
returnType: R;
paramCount: P["length"];
}
: never;
type Info = FunctionInfo<(name: string, age: number) => boolean>;
// { params: [name: string, age: number]; returnType: boolean; paramCount: 2 }
type Info2 = FunctionInfo<() => void>;
// { params: []; returnType: void; paramCount: 0 }
פתרון תרגיל 7¶
א-ב. FormField ו-FormFields:
interface FormField<T> {
value: T;
error: string | null;
touched: boolean;
dirty: boolean;
}
type FormFields<T> = {
[K in keyof T]: FormField<T[K]>;
};
interface LoginForm {
username: string;
password: string;
rememberMe: boolean;
}
type LoginFormFields = FormFields<LoginForm>;
ג. FormErrors:
ד. FormValues:
type FormValues<T> = {
[K in keyof T]: T[K] extends FormField<infer V> ? V : never;
};
type LoginValues = FormValues<LoginFormFields>;
// { username: string; password: string; rememberMe: boolean }
ה. initForm:
function initForm<T extends Record<string, unknown>>(initialValues: T): FormFields<T> {
let fields = {} as FormFields<T>;
for (let key in initialValues) {
(fields as any)[key] = {
value: initialValues[key],
error: null,
touched: false,
dirty: false
};
}
return fields;
}
let form = initForm({ username: "", password: "", rememberMe: false });
console.log(form.username);
// { value: "", error: null, touched: false, dirty: false }
ו. validateForm:
type ValidatorMap<T> = Partial<{
[K in keyof T]: (value: T[K] extends FormField<infer V> ? V : never) => string | null;
}>;
function validateForm<T extends Record<string, FormField<any>>>(
fields: T,
validators: ValidatorMap<T>
): FormErrors<T> {
let errors: FormErrors<T> = {};
for (let key in validators) {
let validator = validators[key];
if (validator && key in fields) {
let field = fields[key];
let error = (validator as any)(field.value);
if (error) {
(errors as any)[key] = error;
}
}
}
return errors;
}
// usage
let loginForm = initForm({ username: "", password: "", rememberMe: false });
let errors = validateForm(loginForm, {
username: (value) => value.length === 0 ? "Username is required" : null,
password: (value) => value.length < 8 ? "Password must be at least 8 characters" : null
});
console.log(errors);
// { username: "Username is required", password: "Password must be at least 8 characters" }