אבל זה רק פסיק קטן

24/07/2020

קבלו שאלת ראיונות עבודה זריזה על ריאקט שעלתה בקורס היום - מה ההבדל בין שתי הקומפוננטות האלה, ובאיזה נעדיף להשתמש:

// Component 1
function App() {
  const [value, setValue] = React.useState(_.random(1, 100));

  return (
    <div>
      <button onClick={() => setValue(c => c + 1)}>{value}</button>
    </div>
  );
}
// Component 2
function App() {
  const [value, setValue] = React.useState(() => _.random(1, 100));

  return (
    <div>
      <button onClick={() => setValue(c => c + 1)}>{value}</button>
    </div>
  );
}

נראה ממש דומה לא?

ההבדל היחיד הוא בקריאה ל useState. בקומפוננטה הראשונה אני מגריל מספר אקראי ומעביר אותו ל useState, ובקומפוננטה השניה אני מעביר פונקציה שמגרילה מספר אקראי ומחזירה אותו.

ולמה ההבדל חשוב? בגירסת הקומפוננטה הראשונה כל פעם שאנחנו מעלים את הערך באמצעות לחיצה על הכפתור ריאקט יקרא שוב לפונקציית הקומפוננטה כדי לקבל את ה Virtual DOM Tree המעודכן שלה. קריאה כזו תפעיל גם את _.random, תיקח את התוצאה ותעביר אותה ל useState. עכשיו בגלל שהקומפוננטה כבר נוצרה אז useState פשוט יזרוק לפח את הפרמטר שקיבל בגלל שכבר יש לו ערך בסטייט ויוצא שסתם התאמצנו להגריל מספר אקראי.

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

התבנית של העברת Callback Function ואיתה העברת השליטה לפונקציה פנימית (במקום להעביר את הערך ממש לפונקציה הפנימית) היא מאוד נפוצה בריאקט ואנחנו מכירים אותה גם מ useMemo ו useCallback. היא שימושית גם באופן כללי בתכנות ולכן כדאי להיות ערניים כשנתקלים בכתיב הזה - זה לא סתם שאנשים התלהבו לכתוב פונקציות חץ בכל מקום, אלא שבאמת יש פה משמעות מבחינת ביצועים.