• בלוג
  • איך למשוך מידע מהשרת בכל מעבר נתיב עם Reach Router

איך למשוך מידע מהשרת בכל מעבר נתיב עם Reach Router

12/11/2020

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

  1. בגישת HTML לשרת, כל נתיב יחזיר בדיוק את אותו קוד צד-לקוח.

  2. דף ה HTML יכיל מידע ראשוני בו אפשר יהיה להשתמש כדי להציג את הדף וכך לא נצטרך בקשת Ajax נוספת אחרי טעינת העמוד. המידע הראשוני נשמר ב HTML בתור JSON.

  3. בגישת Ajax לכל נתיב השרת שולח רק את המידע בלי דף ה HTML שמסביבו.

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

חדי העין שבין הקוראים יכולים לזהות את ה Anti Pattern בארכיטקטורה: ריאקט ראוטר לא אוהב שמנסים להריץ קוד "הכנה" לפני שעוברים נתיב. בסופו של דבר ההרפתקה ההיא הסתיימה בזה שזרקתי את React Router ובחרתי ב Router יותר גמיש (נקרא nighthawk ומאוד לא מתוחזק) שדווקא נתן לי לבנות בדיוק את המנגנון שרציתי.

ארבע שנים קדימה ואני שוב בונה ארכיטקטורת צד שרת דומה. הפעם מייקל ג'קסון וריאן פלורנס כבר לא עובדים ביחד והראוטר החדש של ריאן פלורנס נקרא Reach Router. ריאקט עברה להשתמש ב Hooks ואני כבר למדתי שעדיף להריץ את קוד ה Ajax אחרי שנכנסים לנתיב החדש, ולבנות את קוד הקומפוננטות כך שידע לטפל טוב במצב שאין לו עדיין Data להציג. שילוב כל הרעיונות האלה התחבר לקוד הבא עבור הקומפוננטה הראשית:

function App(props) {
  const location = useLocation();
  const loadedUrl = useSelector(state => state.url);
  const dispatch = useDispatch();

  useEffect(function() {
    let active = true;

    if (location.pathname === loadedUrl) {
      return;
    }
    axios.get(location.pathname).then(function(response) {
      if (active) {
        const data = response.data;
        dispatch(actions.newAppstateReceived(data));
      }
    });
    return function abort() {
      active = false;
    }
  }, [location.pathname, loadedUrl]);

  return (
    <Router>
      <Home path="/" />
      <Post path="/posts/:id" />
    </Router>
  )
}

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

  1. כל מה שזז נמצא בקוד הקומפוננטה בעזרת useEffect. אני מחזיק הרבה פחות Middlewares וכמעט שלא משתמש ב thunk.

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

  3. הקוד עובד בגלל שאני שומר ב State את הנתיב שממנו כל הסטייט הזה הגיע, ובגלל שכל קומפוננטה יודעת להציג דף "ריק" כשאין לה מידע. גישה כזו דורשת יותר זהירות בעת כתיבת הקומפוננטות.

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

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