מחזור החיים של הקומפוננטה בעיניים של מהגר מVue.js לריאקט

השוואה בין מחזור החיים של הקומפוננטה בVue.js לקונספט שעומד מאחורי useEffect של ריאקט.

מחבר אחיה חביבסקרפינג נעים חברים

הכל התחיל כשחיפשתי תשובות:

כמפתח בVue.js שהיגר לריאקט, יצאתי לחפש תשובות..

סליחה, איפה אני אמור לקרוא פה לשרת?

איפה ההוקים שמאפשרים לי גישה למעגל החיים של הקומפוננטה?

קיבלתי תשובות, אבל לשאלות אחרות:
מהו side-effect? מהו תכנות פונקציונאלי? ולבסוף גם איזה בעיות פותר ההוק useEffect, ולמה הוא יותר טוב מההוקים הקודמים לו שנתנו למפתחי ריאקט גישה למעגל החיים של הקומפוננטה?
ועוד מסקנות מעניינות, כמו.. האם Vue.js יותר אינטואטיבית ופשוטה להבנה מריאקט? ולמה אני ממליץ למתכנתים מתחילים להתחיל איתה את מסלול הלמידה שלהם?

Vue.js אינטואטיבית ופשוטה להבנה

היי.. זה לא אני אמרתי, כך טוענים כל המי ומי..

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

מעגל החיים של הקומפוננטה,

כמפתח Vue מתחיל, המושג, מעגל החיים של הקומפוננטה הצית את דמיוני, מי לא מכיר את שיר הפתיחה של מלך האריות, או את גילגול החיים המקסים של הפרפר
מתחיל מביצה=> הופך לזחל  => מתעדכן לגולם => מתעדכן לפרפר => מת

 

גם בקומפוננטה שלנו, קונספט דומה, המורכב מכמה שלבים:

  • איתחול - initialize or create

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

  • הצבה בmount - DOM

בשלב זה השינוי כבר התרחש בHTML שלנו, וכעת ניתן יהיה לגשת לאלמנט החדש שנוצר על ידי הDOM API.

  • עידכון - update

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

  • הסרה מהunMount - DOM

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

 

כיצד זה מתבטא בVue?

Vue מספקת לנו מספר רב של מתודות שנקראות The life cycle hooks" ,hooks", שהם בעצם נקודות גישה בהן אנחנו יכולים להגדיר לפריימוורק איזה פונקציות אנחנו רוצים להפעיל בנקודות מסוימות במעגל החיים של הקומפוננטה. Vue הולכת מאחורי הקלעים ומפעילה פונקציה שמפעילה את המתודות האלה רגע לפני או רגע אחרי שאירוע מסויים במחזור החיים התרחש, וכך הפונקציות שהכנסנו לתוך ההוק נקראות בזמן.

בסך הכל קונספט פשוט להבנה, וכמו שאמרתי, Vue מספקת לנו מספר רב של מתודות כאלה, עם שמות קריאים ופשוטים לפי הסדר זה.

beforeCreate() ,  created() ,  beforeMount() ,  mounted() ,  beforeUpdate() ,  updated() ,  beforeDestroy() ,  destroyed()
 

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

אגב חוץ מאלה Vue מציעה עוד הוקים, ואתם מוזמנים לקרוא עליהם בדוקומנטציה.

 

דוגמאות לשימוש בהוקים בVue

 

ניתן שתי דוגמאות בקוד, רק כדי להדגים איך זה עובד פרקטית (מה שאני מוצא כדבר מאוד יעיל, בקריאת מאמרים מסוג זה), 

created :

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

לדוגמא:

 

<script>
export default {
name: "hooks",
 async created() {
   const response = await fetch('https://myserver.io/api/posts/life-cycle-vs-use-effect')
   const postData = await response.json()
   // here do what ever you want with the data
 },
}
</script>

 

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

mounted :

במתודה mounted, לדוגמא, מומלץ לבצע פעולות הדורשות שימוש בDOM API שכן רק לאחר שלב ההצבה בDOM ניתן לגשת אליו, נוכל כעת לבצע פעולות כמו addEventListener או getElementById
באחת האפליקציות הראשונות שאני כתבתי לדוגמא, בניתי משחק סנייק וטטריס, בגלל האמור לעיל, את השליטה במקשי החיצים הגדרתי בmounted 

 

mounted() {
 window.addEventListener('keydown', (e) => {
   if (e.key == 'ArrowUp') {
     this.turn('up')
   }
   if (e.key == 'ArrowLeft') {
     this.turn('left')
   }
   if (e.key == 'ArrowDown') {
     this.turn('down')
   }
   if (e.key == 'ArrowRight') {
     this.turn('right')
   }
 });
}

 

המימוש של ה"Life cycle hooks" בריאקט

גם ריאקט מתייחסת למחזור החיים של הקומפוננטה, בעיקר בגלגול היותר ותיק של ריאקט, לפני react-hooks. אני מעוניין להתמקד בעיקר בפתרון שמספקת ריאקט הוקס, אך לשם ההבנה של הפתרון, אכתוב כאן בקצרה על ההוקים בclass component, אך לפני כן, קצת רקע למי שלא מכיר את ריאקט.

 

קצת רקע- 

נתחיל מזה, שכיום יש שתי דרכים לכתוב קומפוננטה בריאקט- 

  1. קומפונטטה מסוג class
  2. קומפוננטה מסוג פונקציה

לא כך היה תמיד. בתחילה, על מנת שיהיה אפשר לכתוב קומפוננטות חכמות בריאקט, שמסוגלות לנהל סטייט משלהן, היה צורך להשתמש בקומפוננטות מסוג class. כבר אז היה ניתן לכתוב function component, רק שקומפוננטה מסוג זה הייתה צריכה להסתמך על הסטייט של קומפוננטה מסוג class אשר גבוהה ממנה בהיררכיה (ומעבירה לה דאטה באמצעות props). 

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

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

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

 

בקומפוננטה מסוג CLASS

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


componentDidMount() ,  shouldComponentUpdate() ,  componentDidUpdate() ,  componentWillUnmount()
 


גם כאן תוכלו למצוא הוקים נוספים בדוקומנטציה של ריאקט

 

לשם ההשוואה אציין כאן שני הבדלים מתבקשים בין Vue לריאקט:

  • ריאקט לא ממליצה לבצע פעולות לפני ההצבה בDOM, ולכן את הקריאות לשרת חיצוני, רצוי לעשות לאחר מכן, במתודה () componentDidMount.  לשם הדיוק, יש לריאקט מתודה שמאפשרת גישה לשלב שלפני ההצבה בDOM, אך היא לא מומלצת לשימוש, ובקרוב (ויתכן וכבר עכשיו) תהפוך ל deprecated.

 

  • עוד הבדל בולט לעין, הוא ריבוי המתודות בVue לעומת ריאקט.  נראה שריאקט מלכתחילה ויתרה על כל מיני נקודות במהלך החיים של הקומפוננטה, ובהמשך אף חזרה בה ממתודות שלא מומלץ ואף לא בטוח להשתמש בהן כמו componentWillMount  ו componentWillUpdate, וזה הופך את הסיפור לקצת יותר פשוט מבVue שמספקת הרבה יותר מתודות, אשר בחלקן לא ברור בכלל מה השימוש, מה שהופך את זה אולי לקצת יותר מסורבל (ואשמח אם מפתחי vue הותיקים יאירו את עיניי ויסבירו לי, מתי משתמשים לדוגמא בbeforeMount)

 

בקומפוננטה מסוג פונקציה

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

 

() useEffect

ריאקט מספקת לנו את המתודה useEffect , שמטרתה לנהל את הside effect של הקומפוננטה.

 

אז מה זה בכלל side effect?

אם את מעגל החיים היה יותר קל להבין והיינו צריכים רק להיזכר במלך האריות, המושג side-effect הוא קצת פחות אינטואטיבי בעיניי. 

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

לדוגמא, פונקציה מסויימת יכולה לקבל קלט X ולהחזיר בתגובה פלט Y אבל תוך כדי ריצה לבצע עוד פעולות נוספות, כמו כתיבה בקונסול, קריאה לשרת, שיגור של טיל, כל הפעולות הנוספות האלה, יקראו side effect.

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

 

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

זה כנראה נושא לפוסט אחר..

 

אז, useEffect אמורה לנהל את אותן פעולות שלא קשורות ישירות לפלט ולקלט של הקומפוננטה, הside effect שלה.

 

איך עושים את זה?

המתודה useEffect מקבלת בפרמטר הראשון, פונקציית callback, בה אנו יכולים להגדיר את הפעולות שאנחנו רוצים לבצע, כמו קריאה לשרת, לוגים, גישה setTimeOut ולsetInterval. 

לדוגמא:

 useEffect(()=>{
   console.log('Hi, from use effect')
})


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

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

const [text, setText] = useState("")

useEffect(()=>{
   console.log('Text is changed')
},[text])

במקרה הזה, רק כאשר חל שינוי באלמנט בסטייט שנקרא text תודפס לנו בקונסול ההודעה "Text is changed".

במידה ונעביר מערך ריק כפרמטר, הפונקציה תתבצע רק פעם אחת, מיד לאחר הצבת הקומפוננטה בDOM מה שמזכיר לנו, את mounted בVue ואת componentDidMount בקומפוננטה מסוג class, וכנראה שנרצה להשתמש בצורה זו לדוגמא, על מנת למשוך נתונים מהדאטה בייס

useEffect(()=>{
   async function fetchSomeData(){
       const response = await fetch('https://myserver.io/api/posts/life-cycle-vs-use-effect')
       const postData = await response.json()
       // here do what ever you want with the data
   }
   fetchSomeData()
},[])

ריאקט, במידה מסויימת, זונחת את הקונספט, של מעגל החיים של הקומפוננטה ומאמצת קונספט אחר, של ניהול הside effect של הקומפוננטה.
לדוגמא היא לא עוסקת (בדוקומנטציה) בכל הקשור לסיום חייה של הקומפוננטה כנושא העיקרי. היא כן עוסקת במצב בו אנחנו רוצים לנקות את ההשלכות של הside effect, כמו לדוגמא מצב בו יצרנו interval, וכעת נרצה להסיר אותו, נוכל להשתמש בreturn ולהחזיר בפונקציה שמוחקת את הinterval ומתוך כך, הבנו איך ניתן להשיג את אותה תוצאה שהייתה מביאה לנו המתודה componentWillUnmount.

לדוגמא:

useEffect(()=>{
   const logAllSecond = setInterval(()=>{
       console.log('one second has past')
   },1000)

   return (()=>clearInterval(logAllSecond))
},[])

 

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

 

הפתרון- ניהול side effect יותר מסודר

ניהול הside effect בצורה זו מאפשרת לנו כתיבה יותר מסודרת של הלוגיקה שאמורה להתבצע. 

מה הכוונה?

 

ריאקט מצביעה על שתי חסרונות בניהול הside effect בקומפוננטה הקלאסית (ומתוך כך גם בVue)

בVue.js המתודה created, יכולה להכיל מספר פונקציות שונות שאין קשר ביניהן, ניתן לכתוב את המתודה הנ"ל רק פעם אחת, מה שגורם לכך שכל הלוגיקות צריכות להיכנס בתוך מתודה אחת.

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

 

כמו כן, בVue ישנם תהליכים, שנצטרך לפצל למתודות השונות לאורך חייה של הקומפוננטה, ניקח לדוגמא, את דוגמת הקוד האחרונה שנתתי למעלה, בה יצרתי interval ומחקתי אותו בסוף, לוגיקה זו צריכה להיכתב בקומפוננטה של Vue בשתי מתודות נפרדות created and beforeDestroy, ובקומפוננטה ריאקטית מסוג פונקציה היא יכולה להיכתב במתודת הוק אחת של useEffect.

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

 

הקונספט השונה של useEffect

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

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

אז מה עדיף?

כפי שמשתמע מתחילת הפוסט, אני סבור שהקונספט בVue   של מעגל החיים יותר פשוט וקל להבנה, ומתווסף לאחד הדברים שעושים את Vue.js לפריימוורק יותר פשוט וקל לקליטה. 

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

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

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

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

זהו, אני מקווה שמי שקרא עד לכאן, הצליח להפיק תועלת מהפוסט

אשמח לקבל תגובות והערות, ואם משהו לא ברור אשמח להסביר, ואף לתקן את הפוסט בהתאם.