תרגיל בריפקטורינג טייפסקריפט

26/05/2024

בואו נתחיל עם ממשק טייפסקריפט הבא של פריט ופונקציה לעדכון הפריט:

interface Item {
    id: number;
    text: string;
    likes: number;
    price: number;
}

function updateItem(item: Pick<Item, 'id'> & Partial<Item>) {
    return fetch(`/api/update/${item.id}`, {
        method: 'POST',
        headers: { contentType: 'application/json' },
        body: JSON.stringify(item)
    });
}

הפונקציה updateItem מקבלת אחד או יותר מהשדות של Item אבל חייבת לקבל את השדה id ושולחת את המידע לשרת לצורך עדכון.

תוכן עניינים

  1. השינוי
  2. פיתרון -

1. השינוי

עכשיו אנחנו רוצים להוסיף לפריט גם תמונה. נשים לב שתמונה מתנהגת אחרת משאר השדות, בהעלאה סוג המידע של תמונה הוא File, אבל כשמקבלים פריט מהשרת סוג המידע הוא string:

interface Item {
    id: number;
    text: string;
    likes: number;
    price: number;
    image: string;
}

interface ItemForUpload {
    id: number;
    text?: string;
    likes?: number;
    price?: number;
    image?: File;
}

בנוסף בשביל להעלות תמונה צריך להגדיר ב HTML טופס:

<form onSubmit={handleSubmit}>
    <input type="file" name="image" />
    <input type="submit" value="Upload" />
</form>

וכשיש אירוע submit צריך לקחת את פרטי הטופס לאוביקט FormData ואותו לשלוח לשרת:

function handleSubmit(e) {
    e.preventDefault();
    const form = e.target as HTMLFormElement;
    const data = new FormData(form);
    fetch(`/api/update/${item.id}`, {
        method: 'POST',
        body: data,
    });
}

התרגיל - בואו ננסה לאחד את שני הממשקים.

2. פיתרון -

בגלל שפונקציית העדכון שמקבלת FormData יכולה לטפל גם בעדכון שאר הפרטים ב Item, עדיף לי להשתמש בה. הבעיה היא ש FormData לא כולל וולידציה על טיפוסים וחבל לי להפסיד וולידציה זו בכל שאר התוכנית, במיוחד אם כבר יש לי הרבה מקומות בתוכנית שמשתמשים בפונקציה updateItem הקיימת.

הפיתרון יהיה לשנות את updateItem כדי שתשמור את החתימה שלה (וכך את הגדרות הטיפוסים), אבל במימוש לקרוא לפונקציה ששולחת FormData וכך למחוק את הקוד הכפול.

צעד ראשון יהיה לבנות ממשק אחד לפונקציית ה update. בשביל להוסיף את ה Image אני מגדיר את הטיפוס:

type ItemForUpload = Pick<Item, 'id'> & Partial<Omit<Item, 'image'>> & { image?: File }

שכולל את כל השדות של Item במצב Partial, חוץ מ image שמשנה את סוגו ל File. פונקציית העדכון עכשיו יכולה להיות:

function update(item: ItemForUpload) {
    const fd = new FormData();
    for (const [key, value] of Object.entries(item)) {
        if (typeof value === 'number') {
            fd.append(key, String(value));
        } else {
            fd.append(key, value);
        }
    }
    updateWithForm(item.id, fd);
}

function updateWithForm(id: number, fd: FormData) {
    return fetch(`/api/update/${id}`, {
        method: 'POST',
        body: fd,
    });
}

קוד חיצוני שהשתמש קודם ב updateItem יכול להמשיך להשתמש ב update ולקבל את אותו Type Safety. קוד שרוצה להעביר טופס יוכל לקרוא ישירות ל updateWithForm.