חמישה דברים שהופכים את קוד ריאקט שלי לקשה להבנה
כשריאקט נכנס לאופנה אחד הטיעונים המרכזיים להשתמש בו היה סדר בקוד. המחשבה לפני ריאקט היתה שאנחנו כותבים קוד שמטפל באירועים, לדוגמה "כל פעם שמשתמש לוחץ על כפתור תעשה X" או "כל פעם שמשתמש גולל את המסך תעשה Y". שיטת האירועים הפכה מאוד קשה לתחזוקה כשהקוד התפרס על פני המון קבצים, וכל קובץ הוסיף עוד ועוד אירועים לאותם כפתורים. יותר גרוע היה שהתחלנו לחבר אירועים לדברים שלא באמת קורים על המסך (כל פעם שמשתנה X מתעדכן תזרוק אירוע).
ריאקט היה אמור לתקן את הבלאגן הזה במעבר לשיטת עבודה דקלרטיבית. במקום להגיד מה לעשות כשקורה משהו, אנחנו פשוט מגדירים איזה מידע יש במערכת ואיך ה UI נגזר מהמידע, וריאקט משלים את החסר. אם בעקבות לחיצה על כפתור משתנה מסוים מתעדכן, אוטומטית ריאקט ידע לעדכן את ה UI לפי הערך החדש. לכאורה ה UI תמיד יהיה עקבי עם המשתנים שלנו ואם נדפיס תמונה של כל המשתנים במערכת נוכל תמיד לדעת מה יהיה המצב על המסך.
זה באמת פתר כל מיני באגים של סינכרון בין מקומות שונים ב UI אבל יצר באגים חדשים ויחודיים לריאקט. הנה 5 תבניות שלדעתי הופכות את קוד ריאקט שאני כותב לקשה מאוד לדיבוג.
1. אפקטים
המקום הראשון שהממלכה הקסומה של ריאקט נסדקת הוא useEffect. וזה לא (רק) הממשק המסורבל שלו. הבעיה עם useEffect היא שאתה אף פעם לא יודע על מה האפקט הולך להשפיע. בעוד ש State הרבה פעמים תחום לקומפוננטה בתוכה הוא נמצא, עם אפקט קומפוננטה יכולה להשפיע על כל דבר בעולם, ובצורה לא עקבית.
דוגמה קלאסית לאפקט היא קריאות רשת: כל פעם שמשתנה State מסוים מתעדכן הקומפוננטה פונה לשרת כדי לקבל מידע חדש ולעדכן בעזרתו משתנה State אחר. בראייה של מערכת אוסף קומפוננטות כאלה יוצרות הרבה קריאות רשת וקשה לעקוב איזה פעולה בדיוק גרמה לאיזו קריאה.
במקרה הכללי ככל שיש לי יותר אפקטים בקוד כך יותר קשה לראות איך פעולה מסוימת מביאה לתוצאה מתאימה לה ויותר קשה לדבר בהגיון על הקוד.
2. סטייטים רחוקים
הסיפור של State בריאקט היה אמור לתת מענה טוב לדינמיות של קומפוננטה. משתמש עושה פעולה, מידע בזיכרון מתעדכן ואז ה UI מצויר מחדש לפי המידע העדכני. כל זה הגיוני בתוך קומפוננטה בודדת.
במצבים יותר מורכבים כשיש לי קומפוננטה עם State שמעבירה את ה State לילדים שלה, ואולי גם הם ממשיכים להעביר את הסטייט הלאה לילדים שלהם, אני מגיע לקוד בו משתמש מבצע פעולה במקום אחד והתוצאה היא שינוי על המסך בהרבה מקומות מפוזרים. מצד אחד זה טוב כי כל השינוי מתואם, אבל מצד שני סטייט מרוחק מביא ליותר מדי render-ים בקומפוננטות לא קשורות וכך במקרים מורכבים לבעיות ביצועים, או להפעלה בטעות של אפקטים.
3. ספריות ניהול סטייט גלובאלי
ספריות ניהול סטייט גלובאלי כמו Redux או MobX היו אמורות לפתור את בעיית הסטייט המרוחק ולתת לי מקום אחד שמנהל את השינוי בסטייט שמשפיע על כל היישום. הבעיה שגם ל MobX וגם ל Redux יש בעיות שלהן (בעיקר בכל מה שקשור לזיהוי שינויים) וכך הן רק עושות יותר נזק מתועלת.
במקום לעדכן משתנה State מרוחק אני עכשיו קורא לפונקציה גלובאלית של MobX ואז מנסה להבין למה השינויים לא גרמו ל Render מחדש במקומות המתאימים ואיך לעקוף את האופטימיזציות שהספריות האלה הכניסו.
יותר מזה, ספריות ניהול הסטייט ובפרט כל תבנית Flux מעודדות עבודה עם אוביקטי Singleton גדולים (כל Store הוא Singleton Object) ויוצרות קוד שקשה לבדוק אותו כל כי בדיקה משפיעה על המצב הגלובאלי.
4. מפתחות
הדרישה של ריאקט לציין "מזהה" לכל פריט מידע שמרונדר ברשימה היא הגיונית באפליקציות מסוג מסוים, אבל ככל שהאפליקציות יצירתיות יותר היא עלולה להפוך למעיקה. כשאין מפתחות טבעיים אנחנו מתאמצים "למצוא" דברים שנראים כמו מפתחות ועוברים להשתמש באינדקס של פריט בלולאה או בטקסט של אחד השדות של הפריט.
שימוש במפתחות באמת עוזר לייצר חיבור בין מה שרואים על המסך לבין האוביקטים שנשמרים בזיכרון, וקשה להאשים את ריאקט שדורש מאיתנו את זה, אבל באפליקציות Client SIde כשהאוביקטים שנשמרים בזיכרון לא תמיד מכילים מפתח ברור, הדרישה הזאת הופכת את הקוד למסורבל ויותר רגיש לבאגים.
במיוחד צריך לשים לב ששינוי במפתח גורם ליצירה מחדש של האלמנט ב DOM, ולכן אם בחרתי מפתח לא נכון אני עלול לאבד פוקוס בתיבת טקסט או לגרום לחלון מודאלי להיעלם ויהיה לי קשה להבין למה. (זה קל כשהבעיה באותה קומפוננטה, הרבה יותר קשה לראות את הבעיות האלה כשהמפתח הלא נכון נמצא 5 קומפוננטות מעליך).
5. רנדר פרופס
תבנית מסוכנת אחרונה לרשימה הזאת היא Render Props שגורמת לנו לכתוב קוד שנראה כמו קומפוננטה אבל למעשה הוא לא. הבעיה עם אלה זה שהספריה שמשתמשת ב Render Props תשתול את קטעי הקוד שלנו בתוך קומפוננטות שלה, עליהן אנחנו לא יודעים שום דבר.
התוצאה היא שכשאני מסתכל על קוד אני לא יודע להגיד מתי הוא הולך להתרנדר, כלומר איך הוא משפיע על ביצועי המערכת וזרימת המידע בה.
מה עושים? בגדול - פחות Hooks, קומפוננטות קטנות יותר ושמשפיעות על עצמן בלבד. לא תמיד זה קל בסיבוב הראשון אבל זה משהו ששווה להתאמץ בשבילו בסבבי Refactoring.