געגועים ל Higher Order Components

08/07/2022

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

לפני Hooks היינו משתמשים בריאקט בתבנית קצת מסורבלת שנקראה Higher Order Components כדי לשתף Stateful Logic בין כמה קומפוננטות, מה שהיום אנחנו עושים בקלות עם Custom Hooks. אבל הרעיון מאחורי Higher Order Components יכול לעזור גם בכתיבת קומפוננטות מודרניות ולאפשר שיתוף קוד טוב ופשוט באפליקציה. ככה זה עובד.

1. מה הרעיון של Higher Order Components

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

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

<WithClock>
    <ShowTimer time={time} />
</WithClock>

כשהמשתנה time הוא state של הקומפוננטה WithClock, ועובר בתור prop ל ShowTimer שמראה אותו. לגבי הגלריה היינו יכולים לקבל מבנה דומה:

<WithClock>
    <Gallery currentImage={time % images.length} images={images} />
</WithClock>

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

2. איך Custom Hook מתמודד עם אותה בעיה

היום בדרך כלל היינו כותבים את הקוד הזה עם Custom Hook, לדוגמה:

function ShowTimer(props) {
    const time = useClock();

    return (
        <p>The time is now ... {time}</p>
    );
}

מה שהופך בזיכרון של ריאקט למבנה של אלמנט יחיד ב Virtual DOM:

<ShowTimer />

אז חסכנו אלמנטים בעמוד והפכנו את הרינדור למהיר יותר, אבל מה הפסדנו בתהליך?

3. החסרון של Custom Hook

הבעיה עם Custom Hook היא העברת הלוגיקה חזרה לתוך הקומפוננטה כך שקשה יותר להשתמש מחדש בקומפוננטה הפנימית בהקשרים אחרים. בחזרה לדוגמה השעון - יכול להיות שתהיה לי קומפוננטה יותר מתוחכמת שמעבירה את השניות באנימציה וגם מראה שעון מעוצב ויפה. עכשיו במקום אחר באפליקציה אני רוצה להציג את אותו שעון אבל הפעם שיספור אחורה כמה זמן עד שמבצע מסוים נגמר.

בגלל שהשעון שלי הוא זה שקורא ל useClock אנחנו בעצם תקועים. ה Hook תמיד מחזיר שעון שסופר קדימה, כי זה מה שהוא מתוכנת לעשות. זאת תהיה נקודה שבה אצטרך לעשות Refactoring ולהוציא את הקריאה ל Hook החוצה כך שאפשר יהיה להשתמש בקומפוננטה גם בלי השעון.

4. הצעה לתבנית: לוגיקה ^ אלמנטים

דרך אחת למנוע את הבעיה הזאת לפני שהיא קורית חוזרת לרעיון של HoC אבל בצורה הרבה יותר פשוטה - במקום לכתוב קומפוננטה אחת של שעון שגם כוללת קוד תצוגה וגם את הלוגיקה והקריאה ל Hook, אנחנו מראש נפריד את הקומפוננטה, כך שתהיה לנו קומפוננטה אחת שמפעילה את ה hook וקומפוננטה אחרת שמטפלת בתצוגה.

בדוגמת השעון זה יהיה:

export default function WithClock(props) {
    const time = useClock();
    return (<ShowTimer time={time} />);
}

function ShowTimer(props) {
    const { time } = props;
    return (<p>The time is now ... {time}</p>);
}

לקוח שמשתמש בקומפוננטה כזאת לא יודע שהיא מופרדת לשתי קומפוננטות. אבל כשמישהו יצטרך רק את קוד התצוגה כדי להציג שעון שסופר אחורה ה Refactoring יהיה הרבה יותר קל כי עשינו את כל ההכנה.