useLayoutEffect היא גרסה של useEffect שנדלקת לפני שהדפדפן צובע מחדש את המסך.
useLayoutEffect(setup, dependencies?)הפניה
useLayoutEffect(setup, dependencies?)
התקשר ל-useLayoutEffect כדי לבצע את מדידות הפריסה לפני שהדפדפן צובע מחדש את המסך:
import { useState, useRef, useLayoutEffect } from 'react';
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0);
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height);
}, []);
// ...פרמטרים
-
setup: הפונקציה עם ההיגיון של האפקט שלך. פונקציית ההגדרה שלך עשויה גם להחזיר פונקציית ניקוי. לפני הוספת הרכיב שלך ל-DOM, React יפעיל את פונקציית ההגדרה שלך. לאחר כל רינדור מחדש עם שינויים תלויים, React יריץ תחילה את פונקציית הניקוי (אם סיפקת אותה) עם הערכים הישנים, ולאחר מכן יריץ את פונקציית ההתקנה שלך עם הערכים החדשים. לפני הסרת הרכיב שלך מה-DOM, React יפעיל את פונקציית הניקוי שלך. -
אופציונלי
dependencies: רשימת כל הערכים התגובתיים שאליהם מתייחסים בתוך הקודsetup. הערכים Reactive כוללים את props, state, ואת כל המשתנים והפונקציות המוצהרות ישירות בתוך גוף הרכיב שלך. אם ה-linter שלך הוא מוגדר עבור React, הוא יוודא שכל ערך תגובתי צוין כהלכה כתלות. רשימת התלות חייבת לכלול מספר קבוע של פריטים ולהיכתב בשורה כמו[dep1, dep2, dep3]. React ישווה כל תלות עם הערך הקודם שלה באמצעות ההשוואהObject.is. אם תשמיט ארגומנט זה, האפקט שלך יפעל מחדש לאחר כל רינדור מחדש של הרכיב.
מחזירה
useLayoutEffect מחזירה undefined.
אזהרות
-
useLayoutEffectהוא Hook, אז אתה יכול לקרוא לו רק ברמה העליונה של הרכיב שלך או Hooks משלך. אתה לא יכול לקרוא לזה בתוך לולאות או תנאים. אם אתה צריך את זה, חלץ רכיב והעבר את האפקט לשם. -
כאשר מצב קפדני מופעל, React יריץ הגדרה אחת נוספת לפיתוח בלבד+מחזור ניקוי לפני ההגדרה האמיתית הראשונה. זהו מבחן מאמץ המבטיח שהלוגיקת הניקוי שלך “משקפת” את היגיון ההתקנה שלך ושהוא עוצר או מבטל את כל מה שההגדרה עושה. אם זה use הוא בעיה, הטמיע את פונקציית הניקוי.
-
אם חלק מהתלות שלך הם אובייקטים או פונקציות המוגדרות בתוך הרכיב, קיים סיכון שהם cause האפקט יפעל מחדש לעתים קרובות יותר מהנדרש. כדי לתקן זאת, הסר את object ו [פונקציה](/reference/react/useEffect#הסרת-תלות-פונקציות מיותרות). אתה יכול גם לחלץ עדכונים state ו-non-reactive לוגיקה מחוץ לאפקט שלך.
-
אפקטים פועלים רק על הלקוח. הם לא פועלים במהלך עיבוד השרת.
-
הקוד בתוך
useLayoutEffectוכל עדכוני state המתוזמנים ממנו חוסמים את הדפדפן מלצבוע מחדש את המסך. כאשר used יתר על המידה, זה גורם לאפליקציה שלך לאט. במידת האפשר, העדיפו אתuseEffect.
שימוש
מדידת פריסה לפני שהדפדפן צובע מחדש את המסך
רוב הרכיבים לא צריכים לדעת את מיקומם וגודלם על המסך כדי להחליט מה לרנדק. הם מחזירים רק כמה JSX. לאחר מכן הדפדפן מחשב את פריסה שלהם (מיקום וגודל) וצובע מחדש את המסך.
לפעמים, זה לא מספיק. תארו לעצמכם הסבר כלים שמופיע ליד אלמנט כלשהו ברחף. אם יש מספיק מקום, הסבר הכלי אמור להופיע מעל האלמנט, אבל אם הוא לא מתאים, הוא אמור להופיע מתחת. כדי להציג את קצה הכלים במיקום הסופי הנכון, עליך לדעת את הגובה שלו (כלומר אם הוא מתאים לחלק העליון).
כדי לעשות זאת, עליך לבצע רינדור בשני מעברים:
- עבד את קצה הכלים בכל מקום (גם במיקום שגוי).
- מדדו את גובהו והחליטו היכן למקם את קצה הכלי.
- רנדר את תיאור הכלים שוב במקום הנכון.
כל זה צריך לקרות לפני שהדפדפן יצבע מחדש את המסך. אתה לא רוצה שה-user יראה את הסבר הכלי זז. התקשר ל-useLayoutEffect כדי לבצע את מדידות הפריסה לפני שהדפדפן צובע מחדש את המסך:
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); // You don't know real height yet
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // Re-render now that you know the real height
}, []);
// ...use tooltipHeight in the rendering logic below...
}הנה איך זה עובד צעד אחר צעד:
Tooltipמעבד עם ה-tooltipHeight = 0הראשוני (לכן תיאור הכלי עשוי להיות ממוקם לא נכון).- React ממקם אותו ב-DOM ומריץ את הקוד ב-
useLayoutEffect. - ה-
useLayoutEffectשלך מודד את הגובה של תוכן הסבר הכלים ומפעיל עיבוד מחדש מיידי. Tooltipמעבד שוב עם ה-tooltipHeightהאמיתי (כך שהסבר הכלי ממוקם נכון).- React מעדכן אותו ב-DOM, והדפדפן מציג לבסוף את הסבר הכלי.
רחפו מעל הכפתורים למטה וראו איך ה-tooltip מתאים את המיקום שלו לפי המקום הזמין:
import { useRef, useLayoutEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import TooltipContainer from './TooltipContainer.js'; export default function Tooltip({ children, targetRect }) { const ref = useRef(null); const [tooltipHeight, setTooltipHeight] = useState(0); useLayoutEffect(() => { const { height } = ref.current.getBoundingClientRect(); setTooltipHeight(height); console.log('Measured tooltip height: ' + height); }, []); let tooltipX = 0; let tooltipY = 0; if (targetRect !== null) { tooltipX = targetRect.left; tooltipY = targetRect.top - tooltipHeight; if (tooltipY < 0) { // It doesn't fit above, so place below. tooltipY = targetRect.bottom; } } return createPortal( <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}> {children} </TooltipContainer>, document.body ); }
שימו לב שלמרות שהרכיב Tooltip צריך לרנדר בשתי מעברים (ראשית, עם tooltipHeight אתחול ל-0 ולאחר מכן עם הגובה הנמדד האמיתי), אתם רואים רק את התוצאה הסופית. זו הסיבה שאתה צריך useLayoutEffect במקום useEffect עבור הדוגמה הזו. בואו נסתכל על ההבדל בפירוט להלן.
Example 1 of 2: useLayoutEffect חוסמת את הדפדפן מלצבוע מחדש את
React מבטיח שהקוד בתוך useLayoutEffect וכל עדכוני state המתוזמנים בתוכו יעובדו לפני שהדפדפן יצבע מחדש את המסך. זה מאפשר לך לרנדר את הסבר הכלי, למדוד אותו ולעבד שוב את הסבר הכלי מבלי שה-user ישים לב לעיבוד הנוסף הראשון. במילים אחרות, useLayoutEffect חוסם את הדפדפן מציור.
import { useRef, useLayoutEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import TooltipContainer from './TooltipContainer.js'; export default function Tooltip({ children, targetRect }) { const ref = useRef(null); const [tooltipHeight, setTooltipHeight] = useState(0); useLayoutEffect(() => { const { height } = ref.current.getBoundingClientRect(); setTooltipHeight(height); }, []); let tooltipX = 0; let tooltipY = 0; if (targetRect !== null) { tooltipX = targetRect.left; tooltipY = targetRect.top - tooltipHeight; if (tooltipY < 0) { // It doesn't fit above, so place below. tooltipY = targetRect.bottom; } } return createPortal( <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}> {children} </TooltipContainer>, document.body ); }
פתרון בעיות
אני מקבל שגיאה: “useLayoutEffect לא עושה כלום בשרת”
המטרה של useLayoutEffect היא לאפשר לרכיב שלך use מידע פריסה לעיבוד:
- עבד את התוכן הראשוני.
- מדדו את הפריסה לפני שהדפדפן צובע מחדש את המסך.
- עבד את התוכן הסופי באמצעות מידע הפריסה שקראת.
כאשר אתה או המסגרת שלך uses עיבוד שרת, אפליקציית React שלך מעבדת ל-HTML בשרת עבור העיבוד הראשוני. זה מאפשר לך להציג את ה-HTML הראשוני לפני שהקוד JavaScript נטען.
הבעיה היא שבשרת, אין מידע על פריסה.
בדוגמה הקודמת, הקריאה useLayoutEffect ברכיב Tooltip מאפשרת לו למקם את עצמו בצורה נכונה (מעל או מתחת לתוכן) בהתאם לגובה התוכן. אם ניסית לעבד את Tooltip כחלק מהשרת הראשוני HTML, זה יהיה בלתי אפשרי לקבוע. בשרת, אין עדיין פריסה! לכן, גם אם תציג אותו בשרת, המיקום שלו “יקפוץ” על הלקוח לאחר שה-JavaScript נטען ופועל.
בדרך כלל, רכיבים המסתמכים על מידע פריסה אינם צריכים להופיע בשרת בכל מקרה. לדוגמה, כנראה שזה לא הגיוני להציג Tooltip במהלך העיבוד הראשוני. זה מופעל על ידי אינטראקציה עם הלקוח.
עם זאת, אם אתה נתקל בבעיה זו, יש לך כמה אפשרויות שונות:
-
החלף את
useLayoutEffectב-useEffect. זה אומר לReact שזה בסדר להציג את תוצאת העיבוד הראשונית מבלי לחסום את הצבע (מכיוון שuse המקורי HTML יהפוך לגלוי לפני שהאפקט שלך ירוץ). -
לחלופין, סמן את הרכיב שלך כלקוח בלבד. זה אומר לReact להחליף את התוכן שלו עד ל-[
<Suspense>](/<Suspense>](/<Suspense>](/<Suspense>](/<Suspense>)(/<Suspense>](/<Suspense>)(/<Suspense>)(/<Suspense>)(/<Suspense>)(/<Suspense>)(/<Suspense>)(/<Suspense>2/) ספינר או נצנוץ) במהלך עיבוד השרת. -
לחלופין, אתה יכול לרנדר רכיב עם
useLayoutEffectרק לאחר הידרציה. שמור עלisMountedstate בוליאני שמאוחל ל-false, והגדר אותו ל-trueבתוך קריאה שלuseEffect. היגיון הרינדור שלך יכול להיות כמוreturn isMounted ? <RealContent /> : <FallbackContent />. בשרת ובמהלך ההידרציה, ה-user יראהFallbackContentשלא אמור לקרוא ל-useLayoutEffect. לאחר מכן React יחליף אותו ב-RealContentשפועל על הלקוח בלבד ויכול לכלול קריאותuseLayoutEffect. -
אם אתה מסנכרן את הרכיב שלך עם מאגר נתונים חיצוני ומסתמך על
useLayoutEffectמסיבות שונות מאשר מדידת פריסה, שקול אתuseSyncExternalStoreבמקום זאת תומך בעיבוד שרת.