Menu

מחזור החיים של קומפוננטה ב-React: mount, עדכון, unmount

כל קומפוננטת React עושה mount (מופיעה), מתעדכנת (מתרנדרת שוב) ועושה unmount (מוסרת). למדו איך כל שלב ממופה ל-useEffect, את מתודות מחזור החיים של class והמקבילות שלהן בהוקים, ואת שלבי ה-render וה-commit.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

למחזור החיים של קומפוננטת React יש שלושה שלבים: mounting (הקומפוננטה מופיעה על המסך בפעם הראשונה), עדכון (היא מתרנדרת שוב כי ה-state, ה-props או הקונטקסט שלה השתנו) ו-unmounting (היא מוסרת). בקומפוננטות פונקציה מגיבים לשלבים האלה עם useEffect: אפקט עם [] רץ אחרי ה-mount, אפקט עם תלויות רץ אחרי עדכונים שבהם הן השתנו, והפונקציה שאפקט מחזיר רצה ב-unmount.

הקונסול מתחת לתצוגה המקדימה מציג כל שלב כשהוא קורה.

הקונסול מתחיל ב-render, count = 0 וב-mounted. כל לחיצה על Count רושמת רינדור ועדכון. Unmount רושם unmounted; Mount again מתחיל מחדש מ-count = 0, כי ה-state נזרק יחד עם הקומפוננטה.

mounting

קומפוננטה עושה mount בפעם הראשונה ש-React מרנדרת אותה במקום מסוים בעץ. React קוראת לפונקציה, יוצרת את צמתי ה-DOM מה-JSX שהיא מחזירה, מכניסה אותם, נותנת לדפדפן לצייר, ואז מריצה את האפקטים של הקומפוננטה.

useEffect(fn, []) הוא המקום להתחיל דברים שצריכים להתקיים כל עוד הקומפוננטה על המסך: מנוי, טיימר, חיבור, בקשת נתונים ראשונה. הוא רץ אחרי הציור, ולכן הוא אף פעם לא מעכב את הפריים הראשון.

עדכון

קומפוננטה מתעדכנת (מתרנדרת שוב) כש:

  • ה-state שלה משתנה,
  • ההורה שלה מתרנדר שוב (כברירת מחדל, כל ילד מתרנדר גם הוא, בין אם ה-props שלו השתנו ובין אם לא),
  • קונטקסט שהיא קוראת משתנה.

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

unmounting

קומפוננטה עושה unmount כשההורה שלה מפסיק לרנדר אותה (כמו ש-{show && <Counter />} עושה למעלה), כשה-key שלה משתנה, או כשה-root עצמו עושה unmount. React מסירה את צמתי ה-DOM שלה, מריצה את ה-cleanup של כל אפקט, וזורקת את ה-state שלה. אם הקומפוננטה מופיעה שוב מאוחר יותר, זה מופע חדש לגמרי.

הורים וילדים

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

ה-mount רושם render Parent, render Child, Child effect, Parent effect. לחצו על Remove וה-cleanups רצים בסדר ההפוך: Parent cleanup, ואז Child cleanup.

שלבי render ו-commit

כל עדכון עובר שני שלבים:

  1. שלב ה-render. React קוראת לקומפוננטות שלכם כדי לחשב את ה-JSX החדש ומשווה אותו לתוצאה הקודמת. שום דבר על המסך עוד לא משתנה. React עשויה לעצור את העבודה הזו, להריץ אותה פעמיים ב-StrictMode, או לזרוק אותה, ולכן הרינדור חייב להיות טהור: בלי בקשות, בלי מנויים, בלי שינויים במשתנים חיצוניים.
  2. שלב ה-commit. React מחילה את ההבדלים על ה-DOM, מחברת refs, ומריצה callbacks של useLayoutEffect, הכל לפני שהדפדפן מצייר. callbacks של useEffect רצים אחרי זה, בדרך כלל אחרי שהדפדפן צייר. כשלחיצה או קלט בדיד אחר גרמו לעדכון, React מריצה אותם במקום זאת מיד אחרי ה-layout effects, כך שהתוצאה מוכנה לפני האירוע הבא.

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

מתודות מחזור החיים של class והמקבילות שלהן בהוקים

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

מתודת classמתי היא רצההמקבילה בקומפוננטת פונקציה
constructorפעם אחת, לפני הרינדור הראשוןuseState(initial), useRef
static getDerivedStateFromPropsלפני כל רינדורלחשב את הערך בזמן הרינדור, או לאפס עם key
renderבכל רינדורגוף הפונקציה
componentDidMountאחרי שהרינדור הראשון ב-DOMuseEffect(fn, [])
shouldComponentUpdateלפני עדכון, יכולה לדלג עליוReact.memo (הוא מחזיר את התשובה ההפוכה: "האם ה-props שווים")
getSnapshotBeforeUpdateממש לפני שה-DOM משתנהאין הוק ישיר; useLayoutEffect עם ref הוא הכי קרוב
componentDidUpdateאחרי כל עדכוןuseEffect(fn, [deps]) (רץ גם אחרי mount)
componentWillUnmountלפני הסרהה-cleanup שמוחזר מ-useEffect
getDerivedStateFromError, componentDidCatchכשילד זורק שגיאהאין: כתבו class של error boundary

הנה class שרושם כל מתודה כשהיא רצה:

mounting רושם constructor, getDerivedStateFromProps, render, componentDidMount. לחיצה רושמת getDerivedStateFromProps, shouldComponentUpdate, render, getSnapshotBeforeUpdate, componentDidUpdate. גרמו ל-shouldComponentUpdate להחזיר false ולחיצות מפסיקות לעדכן את הכפתור: ה-state משתנה, אבל React מדלגת על הרינדור. הדף על קומפוננטות class מראה איך ממירים class כזה לפונקציה.

חשבו במונחים של סנכרון, לא מחזור חיים

טבלת ההוקים שימושית לקריאת קוד ישן, אבל היא יכולה להוביל אתכם לכתוב אפקטים שמעתיקים הרגלים של class, כמו אפקט "did mount" גדול אחד ואפקט "did update" גדול אחד. עדיף לקרוא אפקט כ-"שמור את הדבר החיצוני הזה מסונכרן עם הערכים האלה": התחבר ל-roomId, וכש-roomId משתנה, התנתק והתחבר שוב. כתבו אפקט אחד לכל דבר שאתם מסנכרנים, רשמו את הערכים שהוא משתמש בהם כתלויות, ותנו ל-React להחליט מתי להריץ אותו.

שאלות נפוצות

מה השלבים במחזור החיים של קומפוננטת React?

mounting (React יוצרת את הקומפוננטה ושמה את הפלט שלה על המסך), עדכון (היא מתרנדרת שוב כי ה-state, ה-props או הקונטקסט שלה השתנו) ו-unmounting (היא מוסרת ומנקה אחריה).

מה המקבילה בהוקים ל-componentDidMount?

useEffect(() => { ... }, []). מערך התלויות הריק גורם לו לרוץ פעם אחת, אחרי שהרינדור הראשון על המסך. הפונקציה שהוא מחזיר היא המקבילה של componentWillUnmount.

מה המקבילה בהוקים ל-componentDidUpdate?

useEffect עם הערכים שחשובים לכם במערך התלויות, כמו [userId]. בניגוד ל-componentDidUpdate, הוא רץ גם אחרי הרינדור הראשון, כך שהוא מכסה mount ועדכון יחד.

באיזה סדר רצים האפקטים של הורה וילד?

React מרנדרת מההורה כלפי מטה, אבל מריצה אפקטים מהילדים כלפי מעלה: האפקט של ילד רץ לפני זה של ההורה שלו. ב-unmount, ה-cleanup של ההורה רץ לפני זה של הילד.

מה הם שלבי ה-render וה-commit?

בשלב ה-render React קוראת לקומפוננטות שלכם כדי לחשב איך המסך צריך להיראות; היא יכולה לעצור את העבודה הזו או לזרוק אותה. בשלב ה-commit היא מחילה את השינויים על ה-DOM ומריצה layout effects; אפקטים רגילים רצים אחרי זה, בדרך כלל אחרי שהדפדפן צייר.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל