• בלוג
  • שכתוב דוגמת תקשורת ב ClojureScript שכתבתי לפני שנתיים

שכתוב דוגמת תקשורת ב ClojureScript שכתבתי לפני שנתיים

19/02/2022

בפוסט ארבע תבניות לקומפוננטות React מתורגמות ל ClojureScript ו Reagent הראיתי איך לעבוד עם ריאייג'נט ולכתוב קומפוננטות ריאקט ב clojurescript. הדוגמה האחרונה באותו פוסט, וזו שבגללה שמרתי מרחק מריאייג'נט בשנתיים האחרונות היתה דוגמה לתקשורת ajax. היא היתה שונה מקוד שאני רגיל לכתוב בריאקט, ואולי בגללי ואולי בגלל ריאייג'נט עבדה פחות טוב מקוד ג'אווהסקריפט מקביל שהייתי כותב. הקושי העיקרי שלי בתרגום הקוד מ JavaScript לריאייג'נט היתה חוסר התמיכה ב Hooks, ובפרט useEffect שהיה לי מאוד חסר.

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

1. גירסה ישנה שלא עבדה מלפני שנתיים

בפוסט שכתבתי לפני שנתיים הראיתי את הגירסה הבאה שלא עבדה:

(defn pokemon-character [{id :id}]
  (r/with-let [
               data (r/atom {})
               active (r/atom true)
               request (go
                         (let [response (<! (http/get 
                                              (str "https://pokeapi.co/api/v2/pokemon/" @id)
                                              {:with-credentials? false}))]
                           (if (true? @active) (reset! data (:body response)) nil)))
               ]
    [:div
     [:p "ID: " @id]
     [:p "Name: " (str (get @data :name))]]))

הבעיה איתה היא שהפקודה with-let קורית רק פעם אחת, ולכן כשה id ישתנה לא תצא בקשת תקשורת חדשה. בעיה קטנה נוספת היא השימוש ב request שזו ספריית תקשורת של cljs, במקום בקוד התקשורת המוכר של הדפדפן.

2. גירסה שעובדת היום

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

(defn use-pokemon [id]
  (let [[data set-data] (react/useState {})
        active? (r/atom true)]
    (react/useEffect (fn []
                       (go
                         (let [response (<p! (js/fetch
                                               (str "https://pokeapi.co/api/v2/pokemon/" id)))
                               data (<p! (.json response))]
                           (if @active? (set-data data))))
                       (fn []
                         (reset! active? false)))
                     (array id))
    data))

בואו נראה מה יש פה:

  1. שימוש ב useState כדי לשמור את המידע שחוזר מהשרת, עובד בדיוק כמו ב JavaScript.

  2. המשתנה active? מוגדר בתור אטום כדי שאפשר יהיה לשנות את הערך שלו. בדרך כלל משתנים ב Clojure הם Immutable, אבל פה באמת הייתי צריך Mutable Data בשביל לאפשר ביטול של האפקט לפי הממשק של useEffect.

  3. הקריאה ל useEffect נראית בדיוק כמו ב JavaScript. הפרמטר הראשון הוא פונקציית האפקט, שמחזירה פונקציית ביטול, והפרמטר השני הוא מערך התלויות.

  4. קוד התקשורת משתמש ב fetch API הרגיל של הדפדפן. המאקרו go הוא המקבילה הקלוז'רסקריפטית ל async והמאקרו "קטן מ p ואז סימן קריאה" הוא המקבילה הקלוז'רסקריפטית ל await.

וזה בסך הכל יפה כי עכשיו אני יכול להשתמש בפונקציה use-pokemon בתוך קומפוננטה באופן הבא:

(defn pokemon [id]
  (let [
        data (use-pokemon id)
        ]
      [:div
       [:p "ID: " (.stringify js/JSON id)]
       [:p "Name " (aget data "name")]]))

והכל עובד והקומפוננטה נשארת נקיה וממוקדת.

מוזמנים לראות את הקוד המלא עם כל ה require-ים בגיסט: https://gist.github.com/ynonp/57c429315d282a82b145a0b94db92cb7