כל מי שעובד עם ריאקט בטח מכיר את "בעיית הרינדורים של ריאקט" אם להיות כנה אני לא חושב שזו בעייה, יש כאלו שיגידו שריאקט מרנדר את הDOM סתם וגורם לבעיות ביצועים (performance) ואז צריך להשתמש בכל מיני טכניקות שנראות קצת מיותרות\ חוזרות על עצמן כמו למשל useMemo, memo .
אז דעתי היא שזה בסדר גמור לרנדר! לא לדאוג מזה בכלל, יש דברים שאין להם כל חשיבות ברמת ביצועים ואין סיבה סתם להעמיס פונקציונליות על כל פיפס(או להעמיס בזיכרון). אז איפה כן יש לזה חשיבות? ואיך אפשר לבדוק זאת?
מה שאפשר למדוד אפשר לשפר – React Profiler
בשביל לשפר את ביצועי הUI שאתם מיישמים אתם ככל הנראה משתמשים בDEV TOOLS של הדפדפן שלכם. אבל לא תמיד נוח להשתמש בו וכן הוא מסובך למדי במידה ורוצים למדוד ביצועים של כל מיני תרחישים שקורים בצד הלקוח. ולכן אני ממליץ לכם להשתמש בתוסף קטן שנקרא React Developer Tool ויכול להיות חלקכם מכירים אותו כי מאד נוח לעבוד איתו, אבל האם יצא לכם לעבוד עם טאב ה profiler ?
לפני שניגש לעבודה עם profiler ונכיר את היכולות שלו , נבנה דוגמה פשוטה מאד על מנת לתרגל ולהגדים וכך נבין טוב יותר את הנושא.
import React, { useState } from 'react'; import './App.css'; import List from './List'; function App() : JSX.Element { const [counter, setCounter] = useState<number>(0); return ( <div className="App"> <div className="counter-container"> <span>{counter}</span> <button onClick={()=>setCounter(counter+1)}>+</button> </div> <List listCount={21} /> </div> ); } export default App;
import React from 'react'; function List(props: {listCount:number}) : JSX.Element { return ( <div className="list-container"> <ul> { Array.from(Array(props.listCount).keys()).map((item)=>{return (<Item key={item} item={item} />)})} </ul> </div> ) } export default List;
import React from 'react' function Item({item}: {item: number}) :JSX.Element { return ( <li>{item}</li> ) } export default Item;
* { box-sizing: border-box; } body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; background-color: #282c34; } .App { text-align: center; } .counter-container { padding: 20px 30px; border-radius: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); background-color: #fff; max-width: 200px; margin: 30px auto; display: flex; justify-content: space-between; } .counter-container span { font-size: 30px; color: #282c34; } .counter-container button { background-color: salmon; width: 40px; height: 40px; border-radius: 50%; border: none; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); font-size: 20px; color: #fff; cursor: pointer; transform: all 0.2s; } .counter-container button:active { background-color: rgb(208, 94, 81); } .list-container ul { list-style: none; padding: 0; max-width: 240px; margin: 0 auto; } .list-container ul li { display: inline-block; padding: 10px 20px; border-radius: 10px; background: #fff; margin: 2px; }
כל מה שיש לנו פה זה בסה"כ רכיב אחד של רשימה שלא עושה שום דבר ואיזה כפתור שמשנה את הSTATE של APP. ומה שקורה במקרה הזה הוא שכל הרכיבים מתרנדרים כולל LIST שאין בו כל שימוש אז אין באמת סיבה לרנדר אותו,.. בואו נודה גם שבמקרה הזה, זה לא באמת משנה. אבל אם תשנו את הרשימה ל20,000 אייטמים, האם זה סבבה שכל קליק הרשימה תטרנדר ? התשובה לזה כבר לא חלקות דעות שאנחנו יכולים למדוד .
איך מודדים עם React Profiler
נתחיל בסקירה קצרה , כך נראה לשונית ה profiler :

אם אצלכם זה נראה שונה, אז זה כי שיניתי מעט ההגדרות, תוכו להגדיר גם dark mode או להישאר עם הלבן. אבל משהו שאתם רוצים לאפשר זה highlight כאשר הרכיבים מתרנדרים וככה אתם יכולים לראות בעיניים אילו מהרכיבים מתרנדרים בו ברגע. לדוגמה:

הכפתור החשוב ביותר עבורנו בשימוש ב react profiler הוא ה record. כפתור בפינה השמאלית למעלה עושה את רוב הקסם. כול שעליכם לעשות הוא ללחוץ עליו ואז לגשת לאפליקציה שלנו וללחוץ על הכפתור של הוספה ולחזור ותוסף ולהפסיק את ההקלטה. ואז תראה כמה דברים מעניינים מאד:

הצבעים ואורכי הברים הינם אינדיקציה לאילו רכיבים לקחו יותר זמן מהאחרים (צבע אפור מציין שרכיב לא רונדר). כמו כן אפשר לראות את שם הרכיב על הברים עצמם וגם ללחוץ על כל בר ולקבל מידע על משך טעינתו.
אם תשנו את כמות האייטמים לנגיר 20 אל. תראו שזמן זה יתארך . וכפי שכולנו יודעים, אין סיבה בכלל שהרכיב יתרפרש בכלל. אז מה עושים?
קודם כל כמובן שאפשר למנוע רינדורים מיותרים בפירוק טוב יותר של הרכיבים והSTATE . אבל לפעמים אין לנו את האפשרות הזו. ולכן מגיעים לעזרתנו הוקים שימושיים כגון useMemo ו- memo.
React Memo, useMemo
מי ששמע את המושג memoization אז זה אותה הטכניקה שריקאט אימצו עם ההוקים הללו. מי שלא שמע , אז הייתי מציע לגלגל קצת על זה , על קצה המזלג, מדובר בטכניקה בעולם הפיתוח שמאפשרת לשמור להאיץ ביצועים על ידי שימוש בזיכרון, אז אני מראש מזהיר ששימוש בהוקים אלו אינו בחינם, זה יעלה לכם בזיכרון ולכן יש לעשות שימוש הולם.
השימוש בMemo
עם Memo נוכל לעטוף רכיבים שלא נרצה שיתרנדרו שלא לצורך . אם המידע לא משתנה בהם אין סיבה לרנדר אותם. להלן המימוש:
import React, {memo} from 'react'; import Item from './Item'; function List(props: {listCount:number}) : JSX.Element { return ( <div className="list-container"> <ul> { Array.from(Array(props.listCount).keys()).map((item)=>{return (<Item key={item} item={item} />)})} </ul> </div> ) } export default memo(List);
ועכשיו כשתנסו להקליט עם profiler אתם תראו שכל הרכיב של LIST לא מתרנדר וכך חסכתם לא מעט בביצועים!. שאלה שאולי נשאלת בראשכם זה למה לא לעטוף את כל הרכיבים עם MEMO וזהו? אז קודם כל לריאקט יש את המכניזם שלה ויש סיבה שהם לא בחרו בכך וכפי שציינתי ממקודם, MEMO עולה לנו, ואנחנו לא רוצים להעמיס על הזכרון בדפדפן ולכן צריך לעשות חשבון מושכל איפה להשתמש בMEMO.
עוד עניין שחשוב לזכור זה שMEMO עושה SALLOW COMPARISON מה שזה אומר שהטיפול הרינדור יעיל עבור משתנים פרימיטיבים. אם נעביר פונקציה או אובייקט מורכב ככל הנראה MEMO לא יעשה את עבודתו כראוי.
React useMemo
כתבנו בעבר פוסט על ההוק הזה ומי שרוצה להיזכר יכול להיכנס מדריך ריאקט – react hooks | useMemo & useCallback
ובהמשך לפוס שלנו על memozation וכפי שציינו שMEMO לא אפקטיפי על פונקציות למשל אז useMemo בא לעזרתנו. נוכל לעטוף פונקציונאליות שימנעו רינדורים מיותרים. כמו כן נוכל גם להגדיר במערך התלויות עבור משתנים שיאזין להם, כמו כן המשתנה(או המשתנים) שיש לו חשיבות לפונקציה שאותה החלטתם לעטוף עם useMemo.
לסיכום – פוסט מודדים ביצועים עם ריאקט – React Profiler
הסיכום שלי הוא פשוט למדי, כל מה שעליכם לעשות זה לאמץ עבודה עם React Profiler . רכיבים שהינם "זולים" ברינדור אין סיבה לשמור בקאש עם memo או useMemo . אבל אם מדדתם וראיתם שהרינדור עלול להיות יקר, אז להשתמש בהתאם. בהצלחה 🐊✌