יום ראשון, 8 באוגוסט 2021

416 State Management in React

שלום רב וברוכים הבאים לפרק מספר 416 של רברסים עם פלטפורמה - התאריך היום הוא ה-27 ביולי 2021, ואנחנו נפגשנו ביוקנעם באולפן הבייתי יחד עם יונתן ואסף - היי חבר’ה, מה נשמע? - יונתן, מה שלומך? מתאושש מהג’ט לג הקטן? - (יונתן) כן, הבאתי לך טובלרונים לאחרי . . . (רן) איזה כיף זה הטופי הזה שנתקע בין השיניים . . . - אז אסף - ברוך הבא! (אסף) תודה רבה



(רן) היום אנחנו נדבר על נושא Frontend-י - אנחנו לא מדברים הרבה על נושאים Frontend-ים, אבל היום אנחנו נקדיש את כל הערב הזה ל-Frontend - ובעיקר, באופן ספציפי - ל-State Management ב-Frontend.

אז עם זה אסף מגיע אלינו - אז אסף, בוא נכיר אותך: מאיפה אתה בא? מה אתה עושה? ספר לנו קצת עליך וקצת על החברה שלך.
  • (אסף) אז אסף קרינצה, אני בא מתל אביב עד לפה ליוקנעם . . .
  • אני מתכנת - התחלתי לתכנת מקצועית בערך לפני עשר שנים
    • התחלתי את הקריירה ב-CheckPoint, הייתי שם בהתחלה בתחום שהוא יותר Security, אחרי זה עברתי להיות מתכנת ואז ראש צוות
    • אחרי זה עבדתי ב-Microsoft וב-Soluto [עדיין טרי - 413 GitOps with Yaron from Soluto]
  • לאורך השנים עבדתי גם ב-Backend וגם ב-Frontend - 
    • וב-Frontend יצא לי להתעסק בהרבה State Management Solutions: לחוות אותם ב-Production, לעבוד איתם בצוות - וגם בפרוייקטים בבית התנסיתי בכל מיני.
  • אחרי Soluto - בעצם בספטמבר האחרון - עזבתי את העבודה, אני ועוד שני חברים טובים שהם גם שותפים, אחד מהם עבד איתי ב-Soluto ואת השני פגשתי עוד ב-CheckPoint - והקמנו חברה בשם livecycle.
  • כש-livecycle זו חברה שעוסקת במוצר עבור צוותי פיתוח - 
    • אנחנו מרימים סביבות - Preview Environments - מתוך ה-Source Code של הלקוחות שלנו
    • בעצם, הרעיון הוא שעבור כל Change, כל Commit - אנחנו עושים תהליך שהוא דומה לתהליך CI, של לבנות - לעשות Build - למוצר, 
      • ואנחנו גם עושים לו Running, בענן.
    • בעצם, יש “סביבה חיה ובועטת” של כל גרסא של המוצר - בענן - שאפשר לשתף אותה.
    • מעבר לזה, On top that - אנחנו שמים כלי קולברציה (Collaboration Tools)
      • לדוגמא, אפשר לדמיין שיש מעצבת בצוות, ומתכנתת
      • המתכנתת עשתה שינוי בקוד - ושולחת למעצבת, שאולי עיצבה את הפיצ’ר הזה - לינק.
      • המעצבת תוכל להיכנס ללינק, לראות גרסא חיה שלו - ממש גרסא של המוצר, לא איזה Mock - וגם תוכל להגיב שם, עם הכלי קולבורציה שלנו
        • היא תוכל לשנות CSS - נגיד “ה-Margin לא מספיק טוב”
        • היא תוכל לעשות Screenshot ישירות, בלי Tooling חיצוני
        • להקליט וידאו
        • כל הדברים האלה באים “בחינם” - בלחיצה של לינק . . . וכל זה רץ לו על הדפדפן.
(רן) אז אתה אומר - יצרת Pull Request או Merge Request, תלוי באיזו פלטפורמה אתה משתמש - ואז באופן אוטומטי נוצר לך איזשהו Preview link שאותו אתה יכול לשלוח, עוד לפני שעשית Merge, זאת אומרת - אתה לא צריך ללכת ועשות Deploy כדי שהסיפור הזה יעבוד, ופה חוסך זמן ומייעל את ה-Cycle . . .
  • (אסף) נכון . . .
(רן) בסדר . . . אצלכם, דרך אגב, יונתן - יש פתרונות בסגנון הזה?
  • (יונתן) Preview כזה אין לנו . . . יש לנו סביבות Pre-Production, שלשם אנחנו מעלים גרסא
    • באמת בדרך כלל אחרי Merge, כמו שאתה תיארת.
  • (רן) כן, אוקיי

אנחנו בעצם נפגשנו פה כדי לדבר על State Management ב-Frontend.
עכשיו, אני מניח שכל מי שמפתח Frontend בעשור האחרון מבין על מה מדובר, אבל בוא נחבר גם את מי שהוא לא מפתח Frontend, זאת אומרת - למה צריך State Management?
יש לי CSS, יש JavaScript, יש HTML . . . על איזה State בדיוק אנחנו מדברים פה? למה צריך State Management?
  • (אסף) אחלה . . . אז אפשר באמת להתחיל מלהגיד מה זה State, ומה זה בעצם עושה באופן כללי, וספציפית באפליקצית Web.
  • אז אפשר להגיד ש-State זו איזושהי “פיסת אינפורמציה”, שמגדירה איך נראית האפליקציה בכל רגע נתון
    • איך היא נראית ואיך היא מתנהגת
  • לדוגמא - זו הדוגמא הקלאסית, כשמדברים על State Management, יש כזה את ה-”Hello World” שזה ה-To-Do Applications - 
    • כשיש לך רשימת To-Do’s כזאת, ה-State יכול להיות האינפורמציה, Array של To-Do’s, כשכל אחד מה-Item-ים ברשימה יכול להיות אובייקט - שיהיה לו Title ויהיה לו “?Is Completed”, האם המשימה בוצעה או לא.
      • זה ה-State.
    • עכשיו, יש לנו את האפלקיציה - היא לוקחת את ה-State הזה, ומרנדרת (Rendering) איזשהו View.
    • אפשר להסתכל על זה כמו איזושהי Pure Function, שעבור כל State תייצר אפליקציה אחרת, ועבור אותו ה-State תייצר בדיוק את אותה אפליקציה.
  • הרעיון של ה-State Management זה איזה-שהם כלים וקונספטים שעוזרים לנו לנהל את הדבר הזה.

(רן) הזכרת מקודם View - אז יש את המודל הקלאסי, ואולי קצת ישן ומאוס, של Model-View-Controller - אז באלגוריה הזאת, ה-State הוא למעשה ה-Model?
  • (אסף) נכון - ה-ה-State הוא למעשה ה-Model, 

(יונתן) אני חושב שפעם, בעצם . . . יש מעיין Shift כזה ל-Frontend  . . . פעם, ה-Database באמת היה ה-Database, ה-Server היה עושה את הלוגיקה, מרנדר אפילו JSP וכל מיני כאלה - וה-Browser היה רק מציג.
עכשיו, יש Shift כזה שמאלה ל-Frontend, ששם כבר הלוגיקה - ולכן חסר שם איזה משהו . . .
(רן) כן, אני מסכים - ככל שיותר לוגיקה עברה ל-Frontend, ככה נדרש יותר תחכום, ולכן גם נדרש איזשהו State Management - וכנראה שעוד הרבה דברים אחרים. לא רק זה, אבל זה לגמרי אחד מהם.
עכשיו - State Management גם מגיע עם רכיבי View בדרך כלל, נכון? יש איזשהו קשר הדוק - אני מניח, למשל, שכל מי שכתב אי-פעם ב-React, כנראה גם מכיר את Redux ואולי גם עוד כמה חבילות.
למה . . . נשאל את השאלה הזאת ככה: האם באמת “הצימוד” הזה הכרחי? מה המוטיבציה לצימוד הזה? מה אנחנו מרוויחים מהצימוד הזה? או לחילופין - האם אפשר להפריד ביניהם, לצורך העניין - להשתמש ב-Redux עם Angular או Whatever, והאם אפשר להרוויח מזה משהו פה?
  • (אסף) אחלה - אז כמו שאמרתי, יש פה שני דברים שונים: אחד - זה האם כשאתה משתמש ב-React, צריך להוסיף לזה, On top of that, גם Redux?; והשני זה “האם Redux עובד גם עם דברים אחרים?”.
  • אז אם אני אענה על השאלה השנייה קודם - Redux הוא לא Coupled ל-React: זו ספרייה שיכולה לעבוד עם כל מיני ספריות, והיא יותר High-Level.
    • יש Binding מ-Redux ל-React - זה נקרא React Redux, שזה מצמד את React ל-Redux, אבל באופן די גורף - לרוב ישתמשו ב-Redux עם React, וזו גם הספרייה פופולארית ביותר.
    • אני הסתכלתי לא מזמן ב-npm, ב-Weekly Downloads, ו-Redux הוא 90% בערך מהנפח של הספריות שהסתכלתי עליהן.
      • אני לא יודע כמה קורלציה יש לזה לשימוש אמיתי, אבל אם זה אומר משהו, אז Redux היא סופר-סופר פופולארית.
  • (רן) כן - ואני חושב, דרך אגב, ש . . . מי שמפתח Frontend בודאי מכיר, אני אתרגם רק למי שאינו מפתח Frontend כנראה - אחד הדברים שהכי קוסמים ב-Redux זה הפשטות שלה
    • זו ספרייה שהיא (א) מאוד מאוד קטנה ו-(ב) יש המון המון Tutorials על איך לכתוב Redux בשביל עצמך - מתוך מטרה שהמפתחים יבינו באיזה כלי הם משתמשים.
    • אז לא כל ספריות ה-JavaScript פשוטות, אבל Redux זו שפה פשוטה - אבל אלגנטית, וזה היופי שלה.

אבל Redux זה חדשות של לפני . . . כמה? 6-7 שנים?
(רן) כן, אז 7 שנים, אולי יותר - ומאז הרבה מאוד דברים קרו - אז בוא נדבר על מה קורה היום . . .
ביום-יום - מה אתה עושה? אתה הולך ופותח פרויקט ו . . . ? דבר ראשון אתה מביא Redux? איך זה נראה?
  • (אסף) אז זו שאלה מצויינת, ובאמת זה תלוי מאוד בצרכים שלך,  של האפליקציה עצמה
    • צריך לשאול מה עושה האפלקיציה? מה המורכבות שלה? האם בכלל צריך State Management?
    • אפשר אפילו לקחת קודם צעד אחורה - ולשאול האם בכלל יש State? לא לכל אפליקציה יש State, כמו שיונתן ציין.
    • פעם, אם אנחנו הולכים ממש אחורה, ל”תחילת האינטרנט” - אז אתרים זה היה משהו מאוד פשוט: זה היה הטקסט, התמונות, מדי פעם היה איזה Form . . . אבל זה מה שקרה.
    • וככל שעבר הזמן, הדפדפן ניהיה דבר מאוד מורכב, מעיין “מפלצת” - זה היום חזק כמו מערכת הפעלה שלמה כמעט, אפילו דפדפנים יכולים היום להריץ Server-ים  . . .  
    • אז דפדפן זה משהו מאוד חזק, ואפשר לעשות איתו דברים מטורפים.
  • אבל קודם כל, השאלה שצריך לשאול, בכלל לפני ששואלים איזו בספרייה משתמשים, זה האם צריך ספרייה כזאת?
  • אם החלטנו שאנחנו משתמשים ב-React, כספרייה - כי גם את זה אנחנו לא חייבים - אז גם React, לכשלעצמה, יש לה פתרונות לניהול State.
    • כי React באה עם כמה APIs נוחים לניהול State - אני מדבר ספציפית על . . . אפשר לעשות את זה בכמה דרכים
      • או בצורה הישנה, שזה דרך ה-Class
      • או דרך Hooks, שזה משהו קצת יותר חדש.
    • אני אדבר על ה-Hooks, כי זה קצת יותר נוח, אבל אותם עקרונות בדיוק אפשר לעשות גם ב-Classes . . .

(רן) נעשה פה רגע איזו עצירה - יונתן, דיברנו קודם זה שלפחות היסטורית, רוב ה-State היה נשאר בצד של השרת והיה מוחזק ב-Database-ים, והייתה איזושהי שכבת Rendering - שגם היא הייתה נמצאת הרבה פעם בצד של השרת.
ואז הזכרת - אסף - שהדפדפנים היום הם דווקא די חזקים, ושאפשר לעשות בהם כמעט הכל - ובין השאר, יש בהם גם Database-ים.
ועכשיו, נשאלת השאלה - האם יש קשר בין ה-State, שעליו אנחנו מדברים, לבין ה-Database-ים שקיימים היום בדפדפנים? לצורך העניין - האם הם שומרים ב-Database-ים המקומיים שלהם את ה-State, או שזה State שהוא מתדנדר (Rendered)? זאת אומרת - ברגע שאתה עושה Refresh לדף, הכל נעלם ומתחילים מחדש?
  • (אסף) ה-State שאני אוהב וה-State-ים שאני מדבר עליהם - הם לרוב נשמרים ב-RAM, בזכרון, וזה אומר שזה יתנדף ברגע שירפרשו (Refresh) את העמוד.
  • כמובן שאפשר את כל ה-State הזה להעביר אל ה-Local Storage או עם פתרונות Database-יים מקומיים שיעשו את זה Persistent over time.
  • זה במיוחד יהיה יותר קל ויותר נעים עם ספריות כמו Redux, ששומרים הכל במקום אחד - אתה עושה לו פשוט Dump וטוען אותו בחזרה.
    • בדרכים אחרות, אם אתה שומר את ה-State שלך בצורה קצת יותר מבולגנת ב-RAM זה אולי יהיה קצת יותר קשה, 
    • אבל אם אנחנו הולכים, נגיד, על משהו כמו Redux, או Recoil, שגם לו יש Snapshot ו-Store שמסדר הכל במקום אחד - או MobX-State-Tree, שזה גם פתרון כזה - יהיה מאוד קל לעשות Dump של הזיכרון הזה אל ה-Local Storage, לדוגמא, שזה Persistent Storage, כמו Database - ולטעון את זה בחזרה כשטוענים את האפליקציה.
    • (רן) “מאוד קל” במובן הזה שזה פשוט String JSON, ואתה יכול לכתוב ולקרוא אותו לעשות Serialization או De-Serialization?
    • (אסף) כן - בגלל שכל ה-State נמצא בעצם באובייקט אחד, אני יכול לעשות לה סריאליזציה (Serialization) ל-JSON, לשמור אותו כ-String ב-Local Storage, לטעון אותו, לעשות לו דה-סריאלזיציה (De-Serialization) - ולהשתמש בו שוב.

(יונתן) אם אני, נניח, מתחיל לכתוב אפליקציה חדשה, ואני עוד לא יודע כמה היא תסתבך, כמה גדול זה יהיה . . . - האם היית ממליץ לי, מההתחלה, להשתמש ב-State Management, או לחכות שזה ממש “יצעק”?
(רן) זה לא כמו השאלה על Unit Testing? . . . “אני מתחיל משהו קטן, איזשהו פרויקט-צד קטן, לא נראה לי שזה הולך להיות מסובך, אני לא כותב טסטים” . . . מפה לשם - אחרי שנה אתה לא מוצא את הידיים ואת הרגליים . . . 
  • (אסף) כן, לגמרי  . . . אז זה עניין של גישה.
  • אני, כשאני מתחיל משהו חדש, אני אוהב להתחיל את זה עם כמה שפחות Boilerplate וכמה שפחות דברים, הכי נקי שיש, וכשאני צריך עוד ועוד דברים - אני עושה
    • אני אבין בעתיד, כנראה, גם מה הצרכים שלי, ואני אדע לבחור איזה מהפתרונות State Management . . . 
    • כי שוב - זה לא סטנדרט . . . אין סטנדרטיזציה בנושא, אז יש כל כך הרבה ספריות וכל כך הרבה דעות, וזה מסוג הדברים שמתכנתים אוהבים להיות מאוד דעתניים כלפיו.

(יונתן) אז המיגרציה (Migration) הזאת, מלהיות בלי State Management לעם - למשהו ש . . . זה מסובך לעשות את זה? זה re-factor ש”ישכיב” את הצוות או שזה “מכה קלה בכנף”?
  • (אסף) אז אני חושב שזה תלוי מאוד ב- State Management solution שאתה בוחר בו
  • לדוגמא, MobX זו אחת הספריות הפופולאריות - כנראה השנייה-הכי-פופולארית אחרי Redux - זה מאפשר לך לעשות את ה-Transition הזה בצורה די נוחה, 
    • כי זה משתמש באיזשהו “קסם” שמאפשר לך לעטוף אובייקטים רגילים של JavaScript ולהפוך אותם ל-”React-ביים”.
    • מה זאת אומרת -”React-ביים” [חוץ ממשהו שממש קשה להעביר לטקסט ככה?] - זאת אומרת שאם ה-State הזה מעודכן, אז ה-View שלנו גם יתעדכן
      • זאת אומרת ששינינו . . . נגיד בדוגמא של ה-To-Do List, שינינו את ה-Data, את ה-Array הזה של ה-To-Dos? - והקומפוננטות (Components), ה-<div>-ים האלה, שמציירות את זה על המסך, תתעדכנה גם כן.
    • ובגלל שזה משתמש באיזשהו “קסם”, שנקרא Proxy Object של JavaScript או Getter ו-Setter של ES5 - אלו שתי דרכים לעשות את זה
      • זה בעצם “דורס התנהגות” של מה שאנחנו עושים, De-reference לאובייקט, כשאנחנו ניגשים אליו.
    • אז מאחורי הקלעים, אתה משתמש בזה כמו אובייקט רגיל, אתה עושה State.ToDos[7].Title - ועורך את זה.
      • ומאחורי הקלעים, MobX עשתה לך Subscription כשהשתמשת בזה עבור הקומפוננטה (Component) שמשתמשת בזה, והיא תדע לעדכן את הקומפוננטה בכל פעם שעדכנת את ה-State.
      • אז זה יהיה מאוד מאוד נוח . . . 
    • יכול להיות שכתבת את הדבר הזה כאובייקט JavaScript רגיל, ואתה רק מוסיף MobX ועוטף את זה בכמה פונקציות שהספרייה מביאה לך - ואתה די מסודר, יש לך State Manager . . . 
  • בספריות אחרות, נגיד Redux, זה משהו שהוא הרבה יותר opinionated, והוא קצת יותר מורכב.
    • אתה צריך לנסות הרבה יותר דברים
    • וזו גם אחת הביקורות הכי גדולות שיש על הספרייה הזאת - זה שצריך ללמוד הרבה, ושאתה צריך לכתוב הרבה קוד בשביל להשתמש בזה.

(רן) בוא נחזור רגע ל”קסם” - כי קסם זה כיף: אז יש Attribute - אתה אומר נגיד, ToDos[1].Value = “לאסוף כביסה”, ואז, בעצם, אתה אומר שיש איזשהו רכיב שעשה איזשהו Subscription ל-Setter הזה, והוא עושה “Hijacking” לקריאה הזאת או עם Proxy או טכנולוגיה אחרת שהזכרת את שמה, והוא בעצם “תופס” את הקריאה הזו, ואולי הוא עושה Set ל-Value - אבל הוא גם מפעיל איזושהי שרשרת של קריאות, שבסופו של דבר מפעילה את ה-UI.
עכשיו, זה נחמד ברמת השימושיות . . . השאלה, אם אתה מכיר את הקונספט הזה, מה שנקרא The Fallacies of distributed systems - שבעצם זה בא ואומר שכאילו אתה מפעיל איזושהי קריאה, ואתה לא יודע שהקריאה הזאת רצה על איזשהו שרת מרוחק, ולכן אתה גם לא יודע מה כל הדברים הרעים שיכולים לקרות בדרך  . . . 
אז אתה לא מטפל נכון בשגיאות, אתה לא יודע כמה זמן זה יקח, אתה . . . זאת אומרת - זה נראה קל, אבל אתה בעצם “מחביא” מאחורי זה הרבה מאוד דברים שגם יכולים להשתבש, ואם אתה לא מבין שזה מה שיקרה, אתה יכול לטעות, זאת אומרת - יהיה לך UI שהוא Sluggish ועוד כל מיני כאלה תופעות . . . אולי לא תטפל נכון בשגיאות וכו’.
אז איך . . . יש פה איזשהו Trade-off בין פשטות השימוש לבין היכולת שלך לשלוט בהתנהגות בצורה שהיא Fine-grained . . . 
  • (אסף) נכון מאוד . . . MobX, כספרייה, זה משהו שהוא יותר Tool, שהוא נורא לא Opinionated.
  • הוא מאפשר לך איזושהי יכולת, שנותנת לך לעשות Subscription ו-Reactiveness ל-State, בלי לעשות הרבה Boilerplate - אבל זה לא אומר שאתה חייב להשתמש בזה בצורה הכי פשוטה.
    • אתה יכול לעטוף את זה בדברים שיעזרו לך לפתור את הבעיות שציינת - של Observability ושל Debugging יותר נוח.
    • האמת שהיוצר של MobX כתב עוד ספרייה, שקוראים לה MobX-State-Tree, שהיא כן Opinionated, ומשתמשת ב-MobX בתור כלי, מאחורי הקלעים, לעשות את הפעולות היותר . . . של ה-Reactivness.
      • אבל היא מאוד Opinionated - יש שם Store, ל-Store יש Type-ים שאתה רושם אותם, איזשהו מודל . . . 
      • אתה רואה בכל פעם לאן כל דבר הולך ואתה יכול ליצור, מתוך זה, Snapshots - בדומה ל-Redux
      • זאת אומרת - זה משלב, באיזשהו מקום, את הכיפיות והקסם של MobX, אבל את ה-Rigidness וה”נוקשות” הזאת של -Redux, שגורמת לך גם לדבג (Debug) קוד בצורה יותר נוחה וגם להבין מה קורה כשדברים משתבשים.

(רן) כשדיברנו בטלפון, בשיחה המקדימה, דיברנו על ספרייה שנקראית Recoil, שהזכרת את שמה מקודם - מה מעניין בה? מה מיוחד בה? מתי אני ארצה להשתמש בה ולא באחרות?
  • (אסף) אחלה, אז Recoil . . .  אולי לפני שנדבר על Recoil, נדבר טיפה על קונטקסט, כי הרבה מהדברים שם הם סוג של פותרים דברים שהיה בעייתי עם קונטקסט, עם New State.
  • אז אם מסתכלים רגע על ה-API ש-Redux מביא איתו Built-in, בלי להתקין שום ספרייה חיצונית, אז יש לנו שני דברים עיקריים
  • עכשיו - useState ו-Context עושים שני דברים קצת שונים - useState מאפשר לכל קומפוננטה (Component) לשמור State לוקאלי עבורה, שהוא Persistent בין Render Calls
    • זאת אומרת שאם אני אקרא ל-Render עוד פעם, יהיה לי את אותו State.
    • ובנוסף, זה נותן לי את ה-Reactiveness הזה, כמו שדיברנו - זה מרכיב חשוב בכל State Management.
    • ברגע שעדכנתי את ה-State, עם פונקציה שה-Hook הזה מחזיר לי - ה-setState - אז React ידע לקרוא לי ל-Rendering - אם ה-State המחודש
      • זאת אומרת שברגע שאני מעדכן את ה-State - אני יודע שה-View יהיה “טרי”, הוא יצייר לי את מה שאני רוצה עם ה-State “הטרי” והחדש.
    • (רן) זאת אומרת - כאילו יש את המצב הראשוני, ואחר כך, על כל שינוי, יקראו לך ותעשה Rendering מחדש.
    • (אסף) בדיוק - אני יכול . . . React מבטיח לי את זה, שזה אחלה, זה מעולה.
    • הבעיה עם useState זה שדברים . . . שאפליקציה, כשהעץ-קומפוננטות של React מתחיל לגדול ולגדול, אני רוצה לפעמים להתחיל לשתף State בקומפוננטות שלפעמים הן גם במיקום רחוק בעץ . . . 
      • שני עלים שיש להם אב-קדמון משותף, נמוך ביותר, שהוא כמה רמות מעל.
    • ואז זה אומר . . . .
  • (רן) אוקיי - אז אני מסתכל על רשימת ה-To-Do - אז אתה רוצה, נגיד, להציג איזשהו View אחד גדול עם ה-To-Dos, ואולי מימין-מלמעלה גם איזשהו תקציר של הרשימה, נגיד כמה אייטמים נשארו un-checked . . . 
    • (אסף) בדיוק - כמה אייטמים נשארו un-checked, ואולי עוד סטטיסטיקות . . . מעיין כזה Dashboard, זו דוגמא מעולה.
    • ובאמת, כדי ששתי הקומפוננטות הללו תכירנה את אותו State - כי אנחנו לא רוצים לשכפל את ה-State, אנחנו יכולים, תיאורטית, לשכפל את ה-State, אבל אז נוצר מצב שבו אני צריך לטפל בעדכון של שני State-ים שונים, וזה יכול ליצור באגים וזה קשה להבין . . . 
      • (רן) מה הבעיה? תיקח ספרייה, בטח יש אחת כזאת שעושה את זה, לא? . . .
    • (אסף) יש ספרייה . . . ב-JavaScript יש ספרייה לכל דבר, לכל שורה של קוד יש ספרייה . . . 
    • (רן) ראיתי לא מזמן איזשהו API שנקרא is-odd - שמחזיר לך אם המספר זוגי או אי-זוגי . . .
    • (אסף) יש את הסיפור המפורסם של left-pad, שזו ספרייה שהקריסה את כל npm, הקריסה מלא עבודה של מלא אנשים  בכל העולם, בגלל שהשתלטו עליה ועשו שם כל מיני דברים . . [היה לא מזמן ב-398 with Danny Grander from Snyk]
  • (רן) בסדר - אז אתה לא רוצה לשכפל את ה-State וזה, אני חושב, ברור - אבל אתה אומר שיש פה בעיה עם ה-Set  . . .
    • (אסף) כן, ומה הבעיה? יש פה שתי בעיות - 
      • אחת זה שאם אני רוצה ששתי קומפוננטות, שנמצאות במיקום מרוחק בעץ, ישתפו את אותו State, אני צריך להתחיל להעביר את ה-State הזה ממקום למקום - זה נקרא Props Drilling, “קדיחת Properties” . . . 
      • זה לא נוח, זה אומר שבכל פעם שאני רוצה להוסיף State אני צריך להוסיף את זה בעוד “מיליון מקומות”, קשה לעקוב אחרי זה, מאיפה זה בא . . .
      • (רן) כן, אתה צריך לקודד . . . למעשה, אתה צריך . . .עכשיו כשאתה אומר, אני נזכר שעשיתי את זה, וזה היה מה-זה מעצבן . . . בכל מקום אתה צריך ללכת ולעשות . . . “לפעפע” את ה-Attribute הזה למטה ולמטה ולמטה . . . .ממש עבודה ידנית מעצבנת . . . 
  • (יונתן) וזו בעיה גם לפעמים בקוד של ה-Server, נכון? כשאתה מאתחל איזשהו Bin, או איזשהו אובייקט, ורק למטה למטה אתה צריך להעביר אותו . . .
    • (יונתן) ואם אתה “מתפתה”, אז אתה שם איזשהו משתנה גלובאלי או Database או משהו כזה, ואז אתה . . 
      • (רן) זה כשאתה מתכנן להתפטר . . . וכשאתה מתפטר אז אתה לא מגלה . . .
      • (יונתן) אז מה האלטרטיבה ל-Props Drilling הזה? . . . 
    • (אסף) אז רק אני אגיד שבנוסף להעברה הזאת, זה גם עניין של Performance, זה בעייתי - כי איך ש-React עובד, ברגע ש-Props משתנה, הוא קורא ל-Render מחדש . . .
      • הוא קורא ל-Render כש-Props משתנה וכ-State משתנה.
    • וכשכל תת-העץ הזה, שבכלל לא משתמש ב-State - כל מה שהוא עושה זה להעביר את זה מפה לשם כשמתרדנר (Renders) - זה פשוט בזבוז של חישוביות . . .
    • אבל באמת כדי לפתור את זה יש איזשהו API שנקרא Context - ו-Context מאפשר להגדיר איזשהו State . . .
      • (רן) הנה המשתנה הגלובאלי שחיפשת, יונתן . . . 
    • (אסף) בדיוק . . .  אז זה משתנה גלובאלי, שבעצם פותר את העניין של ה-Drilling, כי אני יכול להגדיר את זה ב”אב הקדמון” המשותף הזה, אני יכול להגדיר שם את ה-Context, וזה אומר שכל חלק בתת-עץ, שהוא צאצא של האב הקדמון המשותף הזה, יכול להשתמש ב-Value של ה-Context.
      • וזה מאפשר לי לפתור את ה-Props Drilling - וגם עם זה יש קצת בעיות . . . 
    • אחת - גם פה יש קצת עניין של Performance, כי אם אני, נגיד . . . לרוב, מה שעושים זה שעושים Provider כזה, ובתוך ה-Provider הזה זו קומפוננטת React רגילה - שהיא בעצמה משתמשת ב-useState שדיברנו עליו
      • אז כדי לעדכן את ה-Value, אני מעדכן את ה-Value איפה שאני שם את ה-Provider, והוא ירדנדר (Render) את כל תת העץ.
      • הוא עדיין ירנדר אותו - כי ככה זה עובד: כי ברגע שהתרנדר אב-קדמון, הוא מרנדר את כל התת-עץ.
    • אפשר לפתור את זה בדרכים שונות, כמו נגיד עם React.memo שהופך קומפוננטות, שמשווה באופן Shallow את ה-Properties, ומרנדר את זה רק אם הם שווים - אבל זה מעצבן, כי אני עכשיו חייב לעשות את זה, גם יש בזה Overhead . . . 
      • יכול להיות שזה לא כזה מעניין, ברוב המקרים, תכל’ס, זה לא מעניין - כי זה לא שווה את ההתעסקות, כי זה לא משנה באמת את חוויית המשתמש, האופטימיזציות האלה.
      • אבל כשיש אפליקציות ענקיות, ש-Rendering הוא מאוד יקר, והדברים האלה מתחילים להציק - אז זה מתחיל להיות בעיה, ואז מתחילים לחשוב מה לעשות.
  • (רן) אבל זה כן משנה את חוויית המפתח, זאת אומרת - או שתצטרך לעשות Props Drilling, או שתצטרך להשתמש ב-Context, שזה - בוא, בינינו - זה משתנה גלובאלי, עם כל המעמסה שבאה עליו.
      • אז כן - למפתח זה אומר איזשהו נטל תחזוקתי
      • (אסף) כן, לגמרי . . . 
      • (רן) אז זו הבעיה . . . הבנו - האקדח מהמערכה הראשונה . . . . בסדר.
  • אז Recoil היא זו שתיקנה את הבעיה הזו?
    • (אסף) Recoil תיקנה חלק מהבעיות האלה, כן . . . 
    • ב-Recoil, זה התחיל מהרצאה שהייתה ב-ReactEurope, שזו אותו כנס ש-Dan Abramov עשה בו את ההרצאה המפורסמת על Redux
    • אז זה בחור מ-Facebook, שסיפר שיש להם כלי ב-Facebook, שהם עושים כל מיני סטטיסטיקות על user-ים.
    • הוא סיפר שם על כל מיני דרישות שהיו להם מהמוצר הזה - והוא רצה להשתמש ב-State וב-Context אבל נתקל בכל מיני בעיות - 
      • אחת מהבעיות הייתה . . . עוד בעיה שלא הזכרנו בנוגע ל-Context זה שאם אנחנו רוצים שה-User ייצור באופן דינאמי State - בוא נדמיין לדוגמא שזו הדוגמא שהוא מביא - 
      • אז לדוגמא, יש לי אפליקציה שאני יכול לצייר בה צורות -  אני יכול לצייר בה עיגול, אני יכול לצייר בה מרובע, וה-User יכול פשוט להכניס עוד צורה
      • הוא יכול גם לעשות לזה Drag, או לשנות לזה את ה-Size . . . אפשר לדמיין מעיין Photoshop כזה . . . 
      • עכשיו - לכל אחד כזה הוא רצה ליצור State משלו - והסיבה שהוא לא רצה את זה ב-State משותף זה בשביל Performance, דיברנו על  זה
        • כי אם יש State משותף אז זה ירדנדר את כולם, וכשאתה מתחיל לעשות דברים כמו Dragging, וזה קורה 60 פעמים בשנייה, אז זה כבר מתחיל לכאוב . . . אתה כבר לא יכול, אתה צריך להתחיל לשחק פה עם Performance Optimizations.
      • אבל הוא אמר “אולי נשתמש ב-Context, ועדיין העניין ב-Context הוא שזה סטאטי, ואתה חייב לדעת מראש כמה Context-ים את צריך ליצור . . . אתה לא יכול ליצור Context באופן דינאמי מתוך קוד.
        • וזו בעיה ל-Use Case שכזה . . . 
      • זו אחת הבעיות . . . וכמובן יש את כל הבעיות שדיברנו עליהן מקודם.
    • אז מה שהם עשו ב-Recoil . . . הם עשו כמה דברים מאוד מעניינים - 
      • אחד - בניגוד לספריות אחרות, ברוב הספריות -  זו ספרייה שהיא מאוד Coupled עם React - אתה לא יכול להשתמש ב-Recoil ללא React
      • ויש לזה כמה יתרונות נחמדים, כי ה-API של זה מאוד מאוד פשוט, והוא מאוד דומה ל-API של React
        • אז במקום useState, אתה תשתמש ב-useRecoilState, וזה יחזיר לך את אותו הדבר - יחזיר לך State ו-setState - וזה מאוד Familiar למי שמכיר את React, אתה לא צריך ללמוד הרבה, בניגוד ל-Redux וגם ל-MobX, שצריך ללמוד דברים.
      • והקונספטים מאוד פשוטים - יש בעצם את ה-State הכי פשוט, שנתנו לזה שם די טוב - קוראים לזה- Atom, כי זה משהו אטומי . . . ובו אתה מגדיר State.
      • עכשיו, אתה מגדיר את זה באופן נפרד, במודול אחר, שהוא לא יושב בתוך הקומפוננטה שלך, ולכן אתה יכול לשתף אותו עם קומפוננטות אחרות
        • פשוט עושים לו Import . . . אתה לא צריך לעשות איזשהו Provider שיושב בעץ-למעלה.
        • כל אחד מהם יכול לעשות לזה Import בנפרד, מבלי שיש את התלות הזאת בעץ.
    • (רן) אוקיי . . . אבל זה דווקא . . . זה לא משהו שיכול לקרות ב-Run-time - ה-Import קורה בזמן הפיתוח, נכון? אתה לא יכול להחליט Ad-hoc ש .  . . אתה יודע, בזמן ריצה, לעשות Import למשהו, נכון? זה קצת מזכיר את הבעיה שהייתה עם ה-Context . . . 
    • (אסף) נכון - בגלל זה יש משהו אחר שקיים ב-Recoil ונקרא atomFamily - זו “משפחה של אטומים” . . .
      • אתה מגדיר atomFamily, והוא מייצר לך Atom-ים באופן דינאמי . . . אתה נותן לו ID, והוא יביא לך את ה-Atom המתאים ל-ID, וככה אתה יכול, ב-Run-time, בלי לדעת מראש, אתה יכול ליצור עוד ועוד Atom-ים ולשתף אותם.
    • (רן) אז למעשה, הוא נותן ל-JavaScript לפתור את הבעיה . . . הוא אומר “תעשה Import, אני לא רוצה לנהל לך את המצב” - תעשה Import, ואם יש לך State משותף, תעשה לו Import משני קבצים שונים או משני רכיבים שונים - ובכלל שעשית Import לאותו רכיב, אז ה-State ישמר . . . זה הקונספט.
  • אוקיי, JavaScript . . . אני שואל את עצמי האם יש פה סכנה ל-Race Conditions למיניהם . . . אם שני אובייקטים מחזיקים . . . טוב, זה כנראה לא קורה ב-JavaScript כי זה רץ בסביבה נפרדת, אז יש לנו פה Event Loop וזה לא יכול לקרות.
    • (אסף) כן . . . גם בנוסף, בדומה ל-Redux, האובייקטים האלה הם Immutable - הם לא יכולים להשתנות לעולם, מרגע שהם נוצרו.
    • (רן) אז איך אתה מעדכן State, אם זה Immutable?
    • (אסף) אתה יוצר חדש . . . בכל פעם שאתה מעדכן State אתה לא משנה אותו - אתה יוצר אובייקט חדש, למעשה.
    • (יונתן) תזכור ש-State is Evil . . . אז . . .

(רן) כן, אבל דיברנו על  . . . . אז בוא רגע נחזור לדוגמא שבה יש לנו רשימת To-Dos . . .  יש לנו בחלק המרכזי של הדף את הרשימה המלאה עם ה-Check-box-ים לידה, ולמעלה מצד ימין אני רוצה להחזיק רק את מספר האייטמים שעדיין לא סיימתי, אוקיי? אז אני כן רוצה לעדכן פה איזשהו State, נכון? אני רוצה שלשניהם יהיה single source of truth - אבל אתה אומר שזה Immutable, אז מה אני עושה? איך אני מעדכן?
  • (אסף) מעולה, אז בוא נלך על הדוגמא שאמרת - אם אנחנו משתמשים בפתרון שהוא mutable, כמו נגיד -Redux או Recoil, אז יש פה שני דברים - 
    • א - יש פה את ה-Counter הזה, של כמה אובייקטים הם Completed - זה מה שנקרא Derived Data: זה Data שאמרנו שאנחנו לא רוצים להחזיק אותו פעמיים, אז אנחנו רוצים לחשב אותו, אנחנו רוצים לחשב אותו מתוך ה-Data האמיתי, שזה ה-Array הזה של ה-To-Do List.
    • גם פה יש עניין של “אנחנו לא רוצים לחשב את זה יותר מדי פעמים, אנחנו רוצים לחשב את זה רק כשדברים ישתנו”, כי זה יקר לחשב דברים, אבל אם נחזור לשאלה הזו, רגע, של “איך אנחנו מעדכנים את זה?”, אז בעצם כדי לעדכן את  . . . כדי להוסיף To-Do חדש, אני צריך להוסיף Array חדש, כי אחרת ה-State לא השתנה . . . 
    • עכשיו, למה זה חשוב ב-Redux? כי ב-Redux, הוא משתמש בעניין הזה של mutability בשביל ליצור Reactiveness . . . 
    • דיברנו על Reactiveness, שזה מתי . . . איך אני יודע שכשה-State משתנה, אני צריך להודיע על הקומפוננטות שמשתמשות בו.
      • אז -Redux משתמש ב-mutability כדי לעשות Shallow comparison - הוא לוקח את ה-Reference של האובייקט, ומשווה את זה - כי זו השוואה מאוד מאוד זולה, זה להשוות שני מספרים - הוא לא צריך לעשות Deep Comparison, ולעבור אובייקט-אובייקט ולראות שזה בדיוק אותו Value, הוא רק משווה את ה-Reference.
      • ולכן, אם אתה עושה את זה Immutable, זה מאוד פשוט ליצור את ה-Reactiveness הזה.
        • כשאתה רוצה לשנות את ה-State, אתה צריך ליצור אובייקט חדש.
  • עכשיו, הדבר הזה הוא נושא ב-Redux, שהוא קצת שנוי במחלוקת . . .כי שוב, דיברנו על Boilerplate - בכל פעם ליצור אובייקט חדש זה יכול להיות מאוד מעצבן, 
    • וזה גם יכול ליצור באגים, כי יכול להיות שבטעות שינית את ה-State, כי JavaScript היא שפה שהיא Stateful, אתה יכול לשנות State, הוא נותן לך את זה ואתה יכול לעשות את זה בטעות - ואז זה ייצור באגים, כי בטעות עדכנת את ה-State במקום לעשות State חדש . . . 
    • ואז ה-Comparison לא יעבוד, ואז תקבל View שהוא לא Fresh, הוא Stale, וזה לא יעבוד לך ואתה לא תבין למה, ואז אתה תחפש בקוד ועד שתמצא את זה . . . זה נורא מעצבן.
    • (יונתן) זאת אומרת שהייתי יכול לקחת את המערך של ה-To-Dos, ולהוסיף עוד איבר ברשימה - וזה לא היה מרנדר את הקומפוננטות כי React לא היה מודע לזה . . .
    • (אסף) בדיוק, לא היית רואה את זה - והיית שובר את הראש “למה זה קרה לי?”.
    • (רן) דרך אגב, בניגוד למה שאמרת קודם על MobX, שבו אם היית משנה משהו, אז בסופו של דבר כן ה-UI היה עושה לזה רפלקציה (Reflection).
    • (אסף) נכון - ב-MobX, הוא משתמש ב-mutability, והוא בעצם משתמש בהתנהגות הזאת כדי ליצור את ה-Reactiveness.
    • (רן) וב-Redux או ב-Recoil זה למעשה Anti-Pattern - אם אתה מפתח שעובר מ-MobX לאחד מאלה, הולכים להיות לך כמה חודשים קשים בהתחלה . . . 
    • (אסף) נכון . . . אבל יש חדשות טובות! אותו בחור שעשה את MobX ואת MobX-State-Tree - הוא עשה גם ספרייה שנקראית Immer, שהיום היא באה בתוך Redux -
      • בגלל ש-Redux . . . אחת מהביקורות הכי גדולות זה כל ה-Boilerplate וכל העבודה שצריך לעשות, Redux עבדו קשה בשביל להוסיף לתוך הספרייה כל מיני כלי-עזר שיעזרו לך עם זה.
      • אחד מהם זה Immer - שמשתמש באותו “קסם” שמשתמשים ב-MobX
      • כדי לעשות Immutable state - אבל בצורה שנוח יותר לאנשים לעשות את בצורה של mutable . . .  מה זה אומר? זה אומר שאתה יכול להשתמש ב-API המוכר של JavaScript, של לעשות State.משהו.משהו = . . . לשנות את האובייקט הקיים
        • בדוגמא שנתנו מקודם - להוסיף איבר למערך
      • אבל מאחורי הקלעים, באמצעות אותם קסמים שדורסים את ההתנהגות של האובייקט, הוא ייצור לך אובייקט חדש, עם רפרנס חדש, וה-Shallow Reference Comparison יעבוד  והכל יהיה כיף!
      • אתה לא צריך ליצור Spread . . . מה שקורה הרבה פעמים זה שאתה יוצר Spread-Operator כדי לעשות Spread לאובייקט הקודם מתוך אובייקט חדש - וזה גם, פעם לא היה את זה, זה חדש, קיים רק כמה שנים, פעם היה צריך לעבוד עוד יותר קשה . . . 
      • אז זה עושה API ממש ממש כייפי
    • היום, Redux זו חווייה הרבה יותר כייפית ממה שהיה כשאני השתמשתי בזה . . . 
      • כשהתכוננתי לפודקאסט, ראיתי שהם עשו שם המון המון עבודה כדי לטפל בבעיות האלה.
  • (רן) אני כבר מצליח לדמיין את הפרסומות - “להרגיש Stateful ולהיות Stateless!”, אבל טוב  . . . [יש מצב . . . פרסומות הרבה פחות מוצלחות מזה כבר רצות היום על איילון]

מעולה, מגניב - אז בוא נעשה רגע סיכום: 
  • בגדול, דיברנו על מה זה State Management, ולמה בכלל צריך את זה בצד של ה-Client
  • דיברנו על זה שלוגיקה עברה לצד של ה-Client, ולכן זה . . .  הדברים מתחילים להיות מסובכים וצריך איזושהי דרך, ככה “לסדר את הקוד”, זה לא יכול להיות הכל ספגטי - jQuery Spaghetti, למי שיצא התענוג . . .
    • אז קשה מאוד לנהל קוד כזה, ולכן נולדו ספריות של State Management
  • דיברנו קצת על React, דיברנו על MobX, שיש להן גישות שונות 
  • ובסופו של דבר על Recoil

אז תודה רבה! כמה מילות סיכום?
  • (אסף) היה לי ממש כיף, אפשר להמשיך לדבר על הנושא הזה עוד המון-המון-המון - זה נושא מאוד Debatable . . .  יש המון פתרונות, כל הזמן קמות ספריות חדשות, זהו . . . תודה רבה!

תודה לך אסף, ושהיה בהצלחה ב-livecycle. להתראות!

 האזנה נעימה ותודה רבה לעופר פורר על התמלול!

אין תגובות:

הוסף רשומת תגובה