5.8 פרויקטים פרויקט
פרויקטים - טייפסקריפט¶
פרויקט 1 - אפליקציית Todo בטייפסקריפט¶
תיאור¶
המירו אפליקציית Todo מג׳אווהסקריפט לטייפסקריפט. המטרה: להוסיף טיפוסים מלאים לכל חלק באפליקציה - מודלים, פונקציות, וניהול state.
דרישות¶
מודל הנתונים¶
הגדירו את הטיפוסים הבאים:
Priority- union literal:"low" | "medium" | "high"TodoStatus- union literal:"active" | "completed" | "archived"Todo- אינטרפייס עם:id: string(readonly)title: stringdescription?: stringpriority: Prioritystatus: TodoStatustags: string[]createdAt: Date(readonly)updatedAt: DatecompletedAt?: DateCreateTodoInput- מבוסס עלOmit<Todo, "id" | "createdAt" | "updatedAt" | "completedAt" | "status">(השדות האלה נוצרים אוטומטית)UpdateTodoInput- מבוסס עלPartial<Pick<Todo, "title" | "description" | "priority" | "tags">>TodoFilter- אינטרפייס עם שדות אופציונליים:status,priority,searchTerm,tags
פונקציות CRUD¶
ממשו את הפונקציות הבאות עם טיפוסים מלאים:
function createTodo(input: CreateTodoInput): Todo
function updateTodo(todo: Todo, updates: UpdateTodoInput): Todo
function deleteTodo(todos: Todo[], id: string): Todo[]
function toggleTodo(todo: Todo): Todo // switches between active/completed
function archiveTodo(todo: Todo): Todo
סינון ומיון¶
function filterTodos(todos: Todo[], filter: TodoFilter): Todo[]
function sortTodos(todos: Todo[], sortBy: "title" | "priority" | "createdAt" | "status"): Todo[]
function searchTodos(todos: Todo[], query: string): Todo[]
סטטיסטיקות¶
interface TodoStats {
total: number;
active: number;
completed: number;
archived: number;
byPriority: Record<Priority, number>;
completionRate: number; // percentage
}
function getTodoStats(todos: Todo[]): TodoStats
אחסון¶
function saveTodos(todos: Todo[]): void // save to localStorage
function loadTodos(): Todo[] // load from localStorage
שימו לב: כשטוענים מ-localStorage, צריך להמיר מחרוזות בחזרה ל-Date. הוסיפו פונקציית עזר עם טיפוסים נכונים.
דגשים¶
- אל תשתמשו ב-
anyבשום מקום - כל פונקציה חייבת להיות מוטייפת לחלוטין
- השתמשו ב-discriminated unions, utility types, ו-generics כשמתאים
- צרו קובץ
types.tsנפרד לכל הטיפוסים
פרויקט 2 - ספריית utility מוטייפת¶
תיאור¶
בנו ספריית utility functions בטייפסקריפט עם דגש על גנריקס וטיפוסים מדויקים. כל פונקציה צריכה להיות גנרית ובטוחה מבחינת טיפוסים.
דרישות¶
מודול מערכים - arrays.ts¶
// returns the first element, or undefined for empty arrays
function first<T>(arr: T[]): T | undefined
// returns the last element, or undefined for empty arrays
function last<T>(arr: T[]): T | undefined
// splits array into chunks of given size
function chunk<T>(arr: T[], size: number): T[][]
// removes duplicates (by reference or by key function)
function unique<T>(arr: T[]): T[]
function uniqueBy<T, K>(arr: T[], keyFn: (item: T) => K): T[]
// groups items by a key extracted from each item
function groupBy<T>(arr: T[], keyFn: (item: T) => string): Record<string, T[]>
// creates a lookup map from an array
function keyBy<T>(arr: T[], keyFn: (item: T) => string): Record<string, T>
// returns intersection of two arrays
function intersection<T>(arr1: T[], arr2: T[]): T[]
// returns difference (items in arr1 but not in arr2)
function difference<T>(arr1: T[], arr2: T[]): T[]
// flattens nested arrays one level deep
function flatten<T>(arr: (T | T[])[]): T[]
// zips two arrays into array of pairs
function zip<A, B>(arr1: A[], arr2: B[]): [A, B][]
// sort by a key (returns new array)
function sortBy<T>(arr: T[], keyFn: (item: T) => number | string): T[]
מודול אובייקטים - objects.ts¶
// picks specified keys from an object
function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>
// omits specified keys from an object
function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K>
// deep clones an object (handle Date, Array, nested objects)
function deepClone<T>(obj: T): T
// deep merges two objects
function deepMerge<T extends object, U extends object>(target: T, source: U): T & U
// maps over object values, preserving keys
function mapValues<T extends object, U>(
obj: T,
fn: (value: T[keyof T], key: keyof T) => U
): Record<keyof T, U>
// filters object entries by predicate
function filterEntries<T extends object>(
obj: T,
predicate: (key: keyof T, value: T[keyof T]) => boolean
): Partial<T>
מודול פונקציות - functions.ts¶
// memoizes a function (cache results by arguments)
function memoize<T extends (...args: any[]) => any>(fn: T): T
// debounce - delays execution until quiet period
function debounce<T extends (...args: any[]) => void>(fn: T, delayMs: number): T
// throttle - limits execution to once per interval
function throttle<T extends (...args: any[]) => void>(fn: T, intervalMs: number): T
// pipe - chains functions left to right
function pipe<T>(value: T, ...fns: ((value: T) => T)[]): T
// retry - retries an async function on failure
function retry<T>(fn: () => Promise<T>, maxRetries: number, delayMs?: number): Promise<T>
מודול Result - result.ts¶
ממשו את דפוס ה-Result עם פונקציות עזר:
type Result<T, E = string> =
| { ok: true; value: T }
| { ok: false; error: E };
function ok<T>(value: T): Result<T, never>
function err<E>(error: E): Result<never, E>
function isOk<T, E>(result: Result<T, E>): result is { ok: true; value: T }
function isErr<T, E>(result: Result<T, E>): result is { ok: false; error: E }
function map<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E>
function flatMap<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E>
function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T
function tryCatch<T>(fn: () => T): Result<T, Error>
דגשים¶
- כתבו טיפוסים מדויקים ככל האפשר - אם פונקציה מקבלת מערך של
numberוהיא צריכה להחזירnumber, הטיפוס צריך לשקף את זה - אל תשתמשו ב-
any(חוץ מאילוצים בחתימות גנריות כמו(...args: any[]) => any) - כל פונקציה צריכה לעבוד עם כל טיפוס (גנריקס)
- ארגנו את הקוד בקבצים נפרדים עם exports
פרויקט 3 - API client מוטייפ¶
תיאור¶
בנו wrapper מוטייפ סביב fetch שמספק type safety מלא - הטיפוסים של ה-request ושל ה-response נקבעים לפי ה-endpoint.
דרישות¶
הגדרת ה-API¶
הגדירו את מבנה ה-API כטיפוסים:
// each endpoint defines its methods and their request/response types
interface ApiSchema {
"/users": {
GET: {
query: { page?: number; limit?: number; search?: string };
response: { users: User[]; total: number };
};
POST: {
body: CreateUserInput;
response: User;
};
};
"/users/:id": {
GET: {
params: { id: string };
response: User;
};
PUT: {
params: { id: string };
body: UpdateUserInput;
response: User;
};
DELETE: {
params: { id: string };
response: { success: boolean };
};
};
"/posts": {
GET: {
query: { userId?: string; page?: number };
response: { posts: Post[]; total: number };
};
POST: {
body: CreatePostInput;
response: Post;
};
};
}
הגדירו את הטיפוסים User, Post, CreateUserInput, UpdateUserInput, CreatePostInput.
ה-API client¶
בנו class ApiClient שמספק מתודות type-safe:
class ApiClient<Schema extends Record<string, any>> {
constructor(private baseUrl: string, private defaultHeaders?: Record<string, string>)
// GET request - returns the response type defined in the schema
get<Path extends keyof Schema>(
path: Path,
options?: { query?: ...; params?: ...; headers?: ... }
): Promise<ApiResult<ResponseType>>
// POST request
post<Path extends keyof Schema>(
path: Path,
body: BodyType,
options?: { params?: ...; headers?: ... }
): Promise<ApiResult<ResponseType>>
// PUT request
put<Path extends keyof Schema>(
path: Path,
body: BodyType,
options?: { params?: ...; headers?: ... }
): Promise<ApiResult<ResponseType>>
// DELETE request
delete<Path extends keyof Schema>(
path: Path,
options?: { params?: ...; headers?: ... }
): Promise<ApiResult<ResponseType>>
}
טיפוסי תוצאה¶
type ApiResult<T> =
| { ok: true; data: T; status: number }
| { ok: false; error: ApiError; status: number };
interface ApiError {
code: string;
message: string;
details?: unknown;
}
Interceptors¶
הוסיפו תמיכה ב-interceptors - פונקציות שרצות לפני ואחרי כל request:
interface RequestInterceptor {
(config: RequestConfig): RequestConfig | Promise<RequestConfig>;
}
interface ResponseInterceptor {
(response: Response): Response | Promise<Response>;
}
// add to ApiClient:
addRequestInterceptor(interceptor: RequestInterceptor): void
addResponseInterceptor(interceptor: ResponseInterceptor): void
שימוש לדוגמה - הוספת token:
client.addRequestInterceptor((config) => {
let token = localStorage.getItem("token");
if (token) {
config.headers["Authorization"] = `Bearer ${token}`;
}
return config;
});
דוגמת שימוש¶
let client = new ApiClient<ApiSchema>("https://api.example.com");
// GET /users - TS knows the response type
let usersResult = await client.get("/users", { query: { page: 1, limit: 10 } });
if (usersResult.ok) {
let users = usersResult.data.users; // User[]
let total = usersResult.data.total; // number
}
// POST /users - TS enforces the body type
let createResult = await client.post("/users", {
name: "Alice",
email: "alice@example.com"
});
// type errors caught at compile time:
// client.get("/nonexistent"); // ERROR: not a valid path
// client.post("/users", { invalid: true }); // ERROR: doesn't match CreateUserInput
דגשים¶
- הטיפוסים צריכים להיות מחמירים - אם ה-schema לא מגדיר POST עבור endpoint מסוים, הקומפיילר צריך לתת שגיאה
- טפלו ב-URL params - החליפו
:idבערך האמיתי - טפלו ב-query string - הוסיפו query params ל-URL
- טפלו בשגיאות רשת, timeout, ותגובות לא תקינות
- השתמשו בדפוס Result במקום לזרוק exceptions
- כתבו דוגמאות שימוש שמדגימות את ה-type safety