useLayoutEffect הוא גרסה של useEffect שרצה מוקדם יותר: מיד אחרי ש-React עדכנה את ה-DOM, אבל לפני שהדפדפן מצייר את הפריים החדש. קוד בתוכו יכול למדוד אלמנטים ולשנות את ה-layout, והמשתמש רואה רק את התוצאה הסופית. הוא חוסם את הציור בזמן שהוא רץ, אז השתמשו בו רק כשצריך את התזמון הזה.
האפקטים מוצהרים בסדר effect ואז layout effect, אבל הקונסול מדפיס את ה-layout effect ראשון בכל רינדור. סדר ההצהרה לא משנה בין שני ההוקים; התזמון שלהם כן.
התחביר
החתימה זהה ל-useEffect: פונקציית setup, cleanup אופציונלי ומערך תלויות.
useLayoutEffect(() => {
// runs after the DOM update, before paint
return () => {
// cleanup, before the next run and on unmount
};
}, [dependencies]);
כל מה שאתם יודעים על תלויות ו-cleanup מ-useEffect חל כאן. ההבדל היחיד הוא מתי React קוראת לו.
מתי כל אחד רץ
אחרי רינדור, React מעדכנת את ה-DOM ואז עושה שלושה דברים בסדר קבוע:
- מריצה את כל ה-cleanups של ה-layout effects ואת ה-layout effects, באופן סינכרוני.
- נותנת לדפדפן לצייר. אם layout effect קבע state, React קודם מרנדרת שוב, עדיין לפני הציור.
- מריצה אפקטים רגילים, בדרך כלל אחרי הציור.
לאורך עץ, כל layout effect רץ לפני כל אפקט רגיל, וילדים רצים לפני ההורים שלהם, כי הורה יכול לסיים רק אחרי שהילדים שלו במקום:
הקונסול מציג קודם את שני ה-layout effects, Child ואז Parent, ואז את שני האפקטים הרגילים באותו סדר. ה-key={n} גורם לכל לחיצה לעשות mount ל-Parent חדש, כך שמקבלים את אותן ארבע שורות בכל פעם. הוסיפו לכל אפקט cleanup שרושם, וראו את הישנים רצים לפני החדשים.
בעיית ההבהוב
דמיינו tooltip שאמור להופיע מעל הכפתור שלו, אלא אם אין מקום מעליו, ואז הוא הולך מתחת. אי אפשר לדעת אם יש מקום עד שה-tooltip נמצא ב-DOM ואפשר למדוד את הגובה שלו. אז הרינדור קורה בשני מעברים:
- מרנדרים את ה-tooltip איפשהו, כדי שאפשר יהיה למדוד אותו.
- מודדים אותו, מחשבים את המיקום האמיתי, קובעים state, ומרנדרים שוב.
עם useEffect, הדפדפן עשוי לצייר בין שני המעברים, והמשתמש רואה את ה-tooltip קופץ מהמיקום הראשון לשני. עם useLayoutEffect, המעבר השני קורה לפני הציור, כך שהמיקום הראשון אף פעם לא מוצג.
רחפו מעל הכפתור העליון: אין מעליו מקום, ולכן ה-tooltip מופיע מתחתיו. רחפו מעל התחתון והוא מופיע מעליו. הקונסול אומר לכם באיזה צד המדידה בחרה. שנו את useLayoutEffect ל-useEffect ורחפו שוב: ה-tooltip יכול עכשיו להבהב בראש התיבה לפריים אחד לפני שהוא זז. במחשב מהיר זה לעיתים קרובות מהיר מדי מכדי לשים לב, וזו בדיוק הסיבה שהבאג הזה משתחרר לפרודקשן ואז מדווח מטלפונים איטיים יותר.
offsetHeight, offsetTop ו-getBoundingClientRect() מכריחים כולם את הדפדפן לחשב layout, ולכן קריאה שלהם ב-layout effect נותנת את הערכים האמיתיים של ה-DOM ש-React בדיוק כתבה.
מדידת אלמנט
אותו דפוס עובד לכל גודל שהרינדור תלוי בו: קוראים אותו עם ref ב-layout effect, שומרים ב-state, ומרנדרים איתו.
לחצו על הכפתור ושורת הרוחב מתעדכנת באותו פריים כמו הטקסט. הדף אף פעם לא מציג טקסט חדש ליד רוחב ישן, כי הרינדור השני קורה לפני הציור. הסירו את [text] ממערך התלויות והמדידה רצה אחרי כל רינדור, כולל זה שה-setWidth שלה עצמה גורם לו; היא נעצרת שם רק כי הרוחב כבר לא משתנה.
העדיפו useEffect
layout effect מעכב את הציור עד שהוא מסתיים, ואם הוא קובע state, גם עד שהרינדור הנוסף מסתיים. אחד איטי גורם לכל עדכון בדף להרגיש איטי. השתמשו ב-useLayoutEffect רק כשכל אלה נכונים:
- האפקט קורא layout (גודל, מיקום, גלילה).
- הוא משנה את מה שמרונדר לפי הקריאה הזו.
- מצב הביניים היה נראה לעין ונראה שגוי.
כל השאר (טעינת נתונים, מנויים, טיימרים, לוגים, אנליטיקס) שייך ל-useEffect. כלל מהיר: התחילו עם useEffect, ועברו רק אם אתם רואים הבהוב.
useEffect רץ בדרך כלל אחרי הציור, אבל לא תמיד. כשהעדכון מגיע מפעולת משתמש בדידה כמו לחיצה או הקשה על מקש, React עשויה להריץ את האפקטים לפני שהדפדפן מצייר. זו אחת הסיבות שהבהוב יכול להופיע במצב אחד ולא באחר.
רינדור בצד השרת
אפקטים אף פעם לא רצים בזמן רינדור בצד השרת, וגם useLayoutEffect לא. השרת שולח את ה-HTML של הרינדור הראשון, וה-layout effect רץ רק אחרי hydration בדפדפן. React 18 ומטה הזהירו "useLayoutEffect does nothing on the server" כשקומפוננטה כזו רונדרה בשרת; React 19 הסירה את האזהרה הזו.
אם הפריים הראשון שמרונדר בשרת היה נראה שגוי בלי המדידה, יש שני תיקונים נפוצים: לרנדר fallback ניטרלי (למשל, להסתיר את ה-tooltip עד שהוא נמדד), או לרנדר את הקומפוננטה רק בצד הלקוח:
function ClientOnly({ children }) {
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
return mounted ? children : null;
}
useInsertionEffect
יש הוק אפקט שלישי, useInsertionEffect, שרץ לפני כל layout effect. הוא קיים בשביל ספריות CSS-in-JS שמייצרות תגיות <style> בזמן ריצה: הכנסת הסגנונות שם אומרת ש-layout effect שמודד אלמנט כבר רואה אותו מעוצב. הוא לא יכול לעדכן state ו-refs עוד לא מחוברים, כך שאין לו שימוש בקוד של אפליקציה. אם אתם לא כותבים ספריית עיצוב, לא תצטרכו אותו.
שאלות נפוצות
מה ההבדל בין useEffect ל-useLayoutEffect?
תזמון. useLayoutEffect רץ באופן סינכרוני אחרי ש-React שינתה את ה-DOM אבל לפני שהדפדפן מצייר, כך שעדכוני ה-state שלו מוחלים לפני שהמשתמש רואה משהו. useEffect רץ בדרך כלל אחרי הציור. ה-API ומערך התלויות זהים.
מתי להשתמש ב-useLayoutEffect?
כשאפקט קורא layout (גודל או מיקום) ומיד משנה את מה שמרונדר לפיו, כמו מיקום tooltip מעל הכפתור שלו או מתחתיו. בלעדיו המשתמש יכול לראות את המיקום הראשון, השגוי, לפריים אחד.
האם useLayoutEffect פוגע בביצועים?
הוא חוסם את הדפדפן מלצייר עד שהוא מסתיים, יחד עם כל רינדור מחדש שהוא מפעיל. layout effect איטי גורם לכל הדף להרגיש איטי, אז השתמשו ב-useEffect אלא אם צריך למנוע הבהוב נראה לעין.
האם useLayoutEffect רץ בשרת?
לא. אף אחד מהאפקטים לא רץ בזמן רינדור בצד השרת. גרסאות ישנות של React הדפיסו אזהרה כשקומפוננטה עם useLayoutEffect רונדרה בשרת; React 19 כבר לא עושה את זה, אבל האפקט עדיין רץ רק בדפדפן.