5.4 פונקציות וצמצום טיפוסים פתרון
פתרון - פונקציות וצמצום טיפוסים¶
פתרון תרגיל 1¶
א. clamp:
function clamp(value: number, min: number, max: number): number {
if (value < min) return min;
if (value > max) return max;
return value;
}
console.log(clamp(5, 1, 10)); // 5
console.log(clamp(-3, 1, 10)); // 1
console.log(clamp(15, 1, 10)); // 10
ב. repeatString:
function repeatString(str: string, times: number = 2): string {
return str.repeat(times);
}
console.log(repeatString("ha")); // "haha"
console.log(repeatString("ha", 3)); // "hahaha"
ג. joinStrings:
function joinStrings(separator: string, ...strings: string[]): string {
return strings.join(separator);
}
console.log(joinStrings(", ", "Alice", "Bob", "Charlie")); // "Alice, Bob, Charlie"
console.log(joinStrings(" - ", "a", "b")); // "a - b"
פתרון תרגיל 2¶
א-ב. MathOperation ופונקציות:
type MathOperation = (a: number, b: number) => number;
let add: MathOperation = (a, b) => a + b;
let subtract: MathOperation = (a, b) => a - b;
let multiply: MathOperation = (a, b) => a * b;
let divide: MathOperation = (a, b) => a / b;
ג. calculate:
function calculate(a: number, b: number, operation: MathOperation): number | null {
if (operation === divide && b === 0) {
return null;
}
return operation(a, b);
}
console.log(calculate(10, 3, add)); // 13
console.log(calculate(10, 3, divide)); // 3.333...
console.log(calculate(10, 0, divide)); // null
ד. applyAll:
function applyAll(initial: number, operations: MathOperation[]): number {
let result = initial;
for (let op of operations) {
result = op(result, 0);
}
return result;
}
// example: start with 5, add 0, multiply by 0 = 0
console.log(applyAll(5, [add, multiply])); // 0
הערה: כשהפרמטר השני תמיד 0, התוצאות לא מאוד מעניינות. הרעיון בתרגיל הוא להבין את הטיפוסים.
פתרון תרגיל 3¶
function stringify(value: string | number | boolean | null | undefined): string {
if (value === null) {
return "null";
}
if (value === undefined) {
return "undefined";
}
if (typeof value === "string") {
return `"${value}"`;
}
if (typeof value === "number") {
return value.toString();
}
// value is boolean here
return value.toString();
}
console.log(stringify("hello")); // '"hello"'
console.log(stringify(3.14)); // "3.14"
console.log(stringify(true)); // "true"
console.log(stringify(null)); // "null"
console.log(stringify(undefined)); // "undefined"
שימו לב: null ו-undefined נבדקים עם === ולא עם typeof, כי typeof null מחזיר "object" (באג היסטורי בג׳אווהסקריפט).
פתרון תרגיל 4¶
א. handleError:
class ValidationError {
constructor(public field: string, public message: string) {}
}
class NetworkError {
constructor(public url: string, public statusCode: number) {}
}
class TimeoutError {
constructor(public url: string, public timeoutMs: number) {}
}
function handleError(error: ValidationError | NetworkError | TimeoutError): string {
if (error instanceof ValidationError) {
return `Validation failed for field '${error.field}': ${error.message}`;
}
if (error instanceof NetworkError) {
return `Network error (${error.statusCode}) fetching ${error.url}`;
}
// error is TimeoutError
return `Request to ${error.url} timed out after ${error.timeoutMs}ms`;
}
console.log(handleError(new ValidationError("email", "Invalid format")));
// "Validation failed for field 'email': Invalid format"
console.log(handleError(new NetworkError("https://api.example.com", 404)));
// "Network error (404) fetching https://api.example.com"
console.log(handleError(new TimeoutError("https://api.example.com", 5000)));
// "Request to https://api.example.com timed out after 5000ms"
ב. isRetryable:
function isRetryable(error: ValidationError | NetworkError | TimeoutError): boolean {
if (error instanceof NetworkError) {
return error.statusCode >= 500;
}
if (error instanceof TimeoutError) {
return true;
}
return false; // ValidationError is not retryable
}
פתרון תרגיל 5¶
א. טיפוסים:
interface SuccessResponse {
status: "success";
data: unknown;
message?: string;
}
interface ErrorResponse {
status: "error";
errorCode: number;
errorMessage: string;
}
interface LoadingResponse {
status: "loading";
progress?: number;
}
interface IdleResponse {
status: "idle";
}
type ApiResponse = SuccessResponse | ErrorResponse | LoadingResponse | IdleResponse;
ב-ג. renderResponse עם exhaustive checking:
function renderResponse(response: ApiResponse): string {
switch (response.status) {
case "success":
return `<div class='success'>Data: ${JSON.stringify(response.data)}</div>`;
case "error":
return `<div class='error'>Error ${response.errorCode}: ${response.errorMessage}</div>`;
case "loading":
if (response.progress !== undefined) {
return `<div class='loading'>Loading... ${response.progress}%</div>`;
}
return `<div class='loading'>Loading...</div>`;
case "idle":
return `<div class='idle'>Ready</div>`;
default:
const exhaustiveCheck: never = response;
return exhaustiveCheck;
}
}
פתרון תרגיל 6¶
א. isString:
function isString(value: unknown): value is string {
return typeof value === "string";
}
let x: unknown = "hello";
if (isString(x)) {
console.log(x.toUpperCase()); // ok - TS knows x is string
}
ב. isNonEmptyArray:
function isNonEmptyArray(value: unknown): value is unknown[] {
return Array.isArray(value) && value.length > 0;
}
ג. isAdmin:
interface User {
name: string;
email: string;
}
interface AdminUser extends User {
permissions: string[];
}
function isAdmin(user: User | AdminUser): user is AdminUser {
return "permissions" in user;
}
let user: User | AdminUser = {
name: "Alice",
email: "alice@example.com",
permissions: ["read", "write"]
};
if (isAdmin(user)) {
console.log(user.permissions); // ok - TS knows user is AdminUser
}
ד. סינון מערך מעורב:
let data: (string | number | null | undefined)[] = ["hello", 42, null, "world", undefined, 7, null];
function isStringValue(value: string | number | null | undefined): value is string {
return typeof value === "string";
}
function isNumberValue(value: string | number | null | undefined): value is number {
return typeof value === "number";
}
function isNotNullish(value: string | number | null | undefined): value is string | number {
return value !== null && value !== undefined;
}
let strings = data.filter(isStringValue); // string[] -> ["hello", "world"]
let numbers = data.filter(isNumberValue); // number[] -> [42, 7]
let defined = data.filter(isNotNullish); // (string | number)[] -> ["hello", 42, "world", 7]
פתרון תרגיל 7¶
א. AppEvent:
interface ClickEvent {
type: "click";
x: number;
y: number;
}
interface KeypressEvent {
type: "keypress";
key: string;
ctrlKey: boolean;
}
interface ScrollEvent {
type: "scroll";
scrollTop: number;
scrollLeft: number;
}
interface ResizeEvent {
type: "resize";
width: number;
height: number;
}
type AppEvent = ClickEvent | KeypressEvent | ScrollEvent | ResizeEvent;
ב. EventHandler:
ג. describeEvent:
function describeEvent(event: AppEvent): string {
switch (event.type) {
case "click":
return `Click at (${event.x}, ${event.y})`;
case "keypress":
let prefix = event.ctrlKey ? "Ctrl+" : "";
return `Key pressed: ${prefix}${event.key}`;
case "scroll":
return `Scrolled to (${event.scrollLeft}, ${event.scrollTop})`;
case "resize":
return `Resized to ${event.width}x${event.height}`;
default:
const exhaustiveCheck: never = event;
return exhaustiveCheck;
}
}
ד. filterEvents:
function isClickEvent(event: AppEvent): event is ClickEvent {
return event.type === "click";
}
function filterEvents<T extends AppEvent>(
events: AppEvent[],
eventType: T["type"]
): T[] {
return events.filter((e): e is T => e.type === eventType);
}
// usage
let events: AppEvent[] = [
{ type: "click", x: 10, y: 20 },
{ type: "keypress", key: "Enter", ctrlKey: false },
{ type: "click", x: 50, y: 100 },
{ type: "resize", width: 1920, height: 1080 }
];
let clicks = filterEvents<ClickEvent>(events, "click");
// clicks: ClickEvent[] -> [{ type: "click", x: 10, y: 20 }, { type: "click", x: 50, y: 100 }]
הערה: הגרסה הגנרית של filterEvents מתקדמת יותר. גרסה פשוטה יותר: