לדלג לתוכן

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:

function createDefaultConfig(): AppConfig {
    return createConfig("light", "en", [], false);
}

ג. 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:

type FormErrors<T> = Partial<Record<keyof T, string>>;

ד. 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" }