useLayoutEffect
is a version of useEffect
that fires before the browser repaints the screen.
useLayoutEffect
는 브라우저가 화면을 다시 채우기 전에 실행되는 버전의 useEffect
입니다.
useLayoutEffect(setup, dependencies?)
Reference참조
useLayoutEffect(setup, dependencies?)
Call useLayoutEffect
to perform the layout measurements before the browser repaints the screen:
브라우저가 화면을 다시 그리기 전에 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);
}, []);
// ...
See more examples below. 아래에서 더 많은 예시를 확인하세요.
Parameters매개변수
-
setup
: The function with your Effect’s logic. Your setup function may also optionally return a cleanup function. Before your component is added to the DOM, React will run your setup function. After every re-render with changed dependencies, React will first run the cleanup function (if you provided it) with the old values, and then run your setup function with the new values. Before your component is removed from the DOM, React will run your cleanup function.setup
: Effect의 로직이 포함된 함수입니다. 셋업 함수는 선택적으로 클린업 함수를 반환할 수 있습니다. 컴포넌트가 DOM에 추가되기 전에 React는 셋업 함수를 실행합니다. 변경된 의존성으로 다시 렌더링할 때마다 React는 (클린업 함수를 정의한 경우) 먼저 이전 값으로 클린업 함수를 실행한 다음, 새 값으로 셋업 함수를 실행합니다. 컴포넌트가 DOM에서 제거되기 전에 React는 클린업 함수를 한 번 더 실행합니다. -
optional
dependencies
: The list of all reactive values referenced inside of thesetup
code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is configured for React, it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like[dep1, dep2, dep3]
. React will compare each dependency with its previous value using theObject.is
comparison. If you omit this argument, your Effect will re-run after every re-render of the component. 선택적dependencies
:setup
코드 내에서 참조된 모든 반응형 값의 목록입니다. 반응형 값에는 props, state, 컴포넌트 본문 내부에서 직접 선언된 모든 변수와 함수가 포함됩니다. 린터가 React용으로 설정된 경우, 모든 반응형 값이 의존성으로 올바르게 지정되었는지 확인합니다. 의존성 목록은 일정한 수의 항목을 가져야 하며[dep1, dep2, dep3]
와 같이 인라인으로 작성해야 합니다. React는Object.is
비교를 사용하여 각 의존성을 이전 값과 비교합니다. 이 인수를 생략하면 컴포넌트를 다시 렌더링할 때마다 Effect가 다시 실행됩니다.
Returns반환값
useLayoutEffect
returns undefined
.
useLayoutEffect
는 undefined
를 반환합니다.
Caveats주의사항
-
useLayoutEffect
is a Hook, so you can only call it at the top level of your component or your own Hooks. You can’t call it inside loops or conditions. If you need that, extract a component and move the Effect there.useLayoutEffect
는 훅이므로 컴포넌트의 최상위 레벨 또는 자체 훅에서만 호출할 수 있습니다. 반복문이나 조건문 내부에서는 호출할 수 없습니다. 필요하다면 컴포넌트를 추출하고 Effect를 그곳으로 이동하세요. -
When Strict Mode is on, React will run one extra development-only setup+cleanup cycle before the first real setup. This is a stress-test that ensures that your cleanup logic “mirrors” your setup logic and that it stops or undoes whatever the setup is doing. If this causes a problem, implement the cleanup function. Strict Mode가 켜져 있으면 React는 첫 번째 실제 셋업 전에 개발 전용 셋업+클린업 사이클을 한 번 더 실행합니다. 이는 클린업 로직이 셋업 로직을 “미러링”하고 설정이 수행 중인 모든 작업을 중지하거나 취소하는지 확인하는 스트레스 테스트입니다. 문제가 발생하면 클린업 함수를 구현하세요.
-
If some of your dependencies are objects or functions defined inside the component, there is a risk that they will cause the Effect to re-run more often than needed. To fix this, remove unnecessary object and function dependencies. You can also extract state updates and non-reactive logic outside of your Effect. 의존성 중 일부가 컴포넌트 내부에 정의된 객체 또는 함수인 경우, Effect가 필요 이상으로 자주 다시 실행될 위험이 있습니다. 이 문제를 해결하려면 불필요한 객체 및 함수 의존성을 제거하세요. 또한 Effect 외부에서 state 업데이트와 비반응형 로직을 추출할 수도 있습니다.
-
Effects only run on the client. They don’t run during server rendering. Effects는 클라이언트에서만 실행됩니다. 서버 렌더링 중에는 실행되지 않습니다.
-
The code inside
useLayoutEffect
and all state updates scheduled from it block the browser from repainting the screen. When used excessively, this makes your app slow. When possible, preferuseEffect
.useLayoutEffect
내부의 코드와 여기에서 예약된 모든 state 업데이트는 브라우저가 화면을 다시 그리는 것을 차단합니다. 과도하게 사용하면 앱이 느려집니다. 가급적이면useEffect
를 사용하세요.
Usage사용법
Measuring layout before the browser repaints the screen 브라우저에서 화면을 다시 그리기 전 레이아웃 측정하기
Most components don’t need to know their position and size on the screen to decide what to render. They only return some JSX. Then the browser calculates their layout (position and size) and repaints the screen. 대부분의 컴포넌트는 무엇을 렌더링할지 결정하기 위해 화면에서의 위치와 크기를 알 필요가 없습니다. 일부 JSX만 반환하기 때문입니다. 그런 다음 브라우저는 해당 컴포넌트의 레이아웃(위치 및 크기)을 계산하고 화면을 다시 그립니다.
Sometimes, that’s not enough. Imagine a tooltip that appears next to some element on hover. If there’s enough space, the tooltip should appear above the element, but if it doesn’t fit, it should appear below. In order to render the tooltip at the right final position, you need to know its height (i.e. whether it fits at the top). 때론 그것만으로는 충분하지 않을 수 있습니다. 마우스오버 시 요소 옆에 툴팁을 표시하는 것을 상상해 보세요. 공간이 충분하면 툴팁이 요소 위에 표시되어야 하지만, 공간이 충분하지 않으면 아래에 표시되어야 합니다. 툴팁을 올바른 최종 위치에 렌더링하려면 툴팁의 높이(즉,상단에 표시하기에 충분한지 여부)를 알아야 합니다.
To do this, you need to render in two passes: 이렇게 하려면 두 번의 패스로 렌더링해야 합니다:
-
Render the tooltip anywhere (even with a wrong position). 툴팁을 원하는 위치에 렌더링합니다(위치가 잘못된 경우에도).
-
Measure its height and decide where to place the tooltip. 높이를 측정하고 툴팁을 배치할 위치를 결정합니다.
-
Render the tooltip again in the correct place. 올바른 위치에 툴팁을 다시 렌더링합니다.
All of this needs to happen before the browser repaints the screen. You don’t want the user to see the tooltip moving. Call useLayoutEffect
to perform the layout measurements before the browser repaints the screen:
이 모든 작업은 브라우저가 화면을 다시 그리기 전에 이루어져야 합니다. 사용자가 툴팁이 움직이는 것을 보지 않기를 원합니다. 브라우저가 화면을 다시 그리기 전에 useLayoutEffect
를 호출하여 레이아웃 측정을 수행합니다:
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); // You don't know real height yet
// 아직 실제 height 값을 모릅니다.
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // Re-render now that you know the real height
// 실제 높이를 알았으니 이제 리렌더링 합니다.
}, []);
// ...use tooltipHeight in the rendering logic below...
// ...아래에 작성될 렌더링 로직에 tooltipHeight를 사용합니다...
}
Here’s how this works step by step: 단계별 작동 방식은 다음과 같습니다:
-
Tooltip
renders with the initialtooltipHeight = 0
(so the tooltip may be wrongly positioned).Tooltip
은 초기tooltipHeight = 0
으로 렌더링됩니다(따라서 툴팁의 위치가 잘못 지정될 수 있음). -
React places it in the DOM and runs the code in
useLayoutEffect
. React는 이를 DOM에 배치하고useLayoutEffect
에서 코드를 실행합니다. -
Your
useLayoutEffect
measures the height of the tooltip content and triggers an immediate re-render.useLayoutEffect
는 툴팁 콘텐츠의 높이를 측정하고 즉시 다시 렌더링을 촉발합니다. -
Tooltip
renders again with the realtooltipHeight
(so the tooltip is correctly positioned).Tooltip
이 실제tooltipHeight
로 다시 렌더링됩니다(따라서 툴팁이 올바르게 배치됩니다). -
React updates it in the DOM, and the browser finally displays the tooltip. React가 DOM에서 이를 업데이트하면 브라우저에 툴팁이 최종적으로 표시됩니다.
Hover over the buttons below and see how the tooltip adjusts its position depending on whether it fits: 아래 버튼 위로 마우스를 가져가면 툴팁이 맞는지 여부에 따라 툴팁의 위치가 어떻게 조정되는지 확인할 수 있습니다:
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 ); }
Notice that even though the Tooltip
component has to render in two passes (first, with tooltipHeight
initialized to 0
and then with the real measured height), you only see the final result. This is why you need useLayoutEffect
instead of useEffect
for this example. Let’s look at the difference in detail below.
Tooltip
컴포넌트가 두 번의 패스로 렌더링되어야 하지만(먼저 tooltipHeight
를 0으로 초기화한 다음 실제 측정된 높이로), 최종 결과만 볼 수 있다는 점에 유의하세요. 이 예제에서 useEffect
대신 useLayoutEffect
가 필요한 이유입니다. 아래에서 차이점을 자세히 살펴보겠습니다.
Example 1 of 2: useLayoutEffect
blocks the browser from repainting useLayoutEffect
는 브라우저가 다시 그리는 것을 차단합니다
React guarantees that the code inside useLayoutEffect
and any state updates scheduled inside it will be processed before the browser repaints the screen. This lets you render the tooltip, measure it, and re-render the tooltip again without the user noticing the first extra render. In other words, useLayoutEffect
blocks the browser from painting.
React는 브라우저가 화면을 다시 그리기 전에 useLayoutEffect
내부의 코드와 그 안에서 예약된 모든 state 업데이트가 처리되도록 보장합니다. 이를 통해 사용자가 첫 번째 리렌더링을 눈치채지 못하게 한 상태에서 툴팁을 렌더링하고, 측정한 후, 다시 툴팁을 렌더링하게 해줍니다. 즉, 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 ); }
Troubleshooting문제 해결
I’m getting an error: “useLayoutEffect
does nothing on the serverˮ 오류가 발생합니다: “useLayoutEffect
가 서버에서 아무것도 수행하지 않습니다ˮ
The purpose of useLayoutEffect
is to let your component use layout information for rendering:
useLayoutEffect
의 목적은 컴포넌트가 렌더링에 레이아웃 정보를 사용하도록 하는 것입니다:
-
Render the initial content. 초기 콘텐츠를 렌더링합니다.
-
Measure the layout before the browser repaints the screen. 브라우저가 화면을 다시 칠하기 전에 레이아웃을 측정합니다.
-
Render the final content using the layout information you’ve read. 읽은 레이아웃 정보를 사용하여 최종 콘텐츠를 렌더링합니다.
When you or your framework uses server rendering, your React app renders to HTML on the server for the initial render. This lets you show the initial HTML before the JavaScript code loads. 사용자 또는 프레임워크가 서버 렌더링을 사용하는 경우, React 앱은 초기 렌더링을 위해 서버의 HTML로 렌더링됩니다. 이를 통해 JavaScript 코드가 로드되기 전에 초기 HTML을 표시할 수 있습니다.
The problem is that on the server, there is no layout information. 문제는 서버에는 레이아웃 정보가 없다는 것입니다.
In the earlier example, the useLayoutEffect
call in the Tooltip
component lets it position itself correctly (either above or below content) depending on the content height. If you tried to render Tooltip
as a part of the initial server HTML, this would be impossible to determine. On the server, there is no layout yet! So, even if you rendered it on the server, its position would “jump” on the client after the JavaScript loads and runs.
이전 예시에서 Tooltip
컴포넌트의 useLayoutEffect
호출은 콘텐츠 높이에 따라 콘텐츠 위 또는 아래에 올바르게 배치되도록 합니다. 초기 서버 HTML의 일부로 Tooltip
을 렌더링하려고 하면 이를 확인할 수 없습니다. 서버에는 아직 레이아웃이 없습니다! 따라서 서버에서 렌더링하더라도 JavaScript가 로드되고 실행된 후 클라이언트에서 그 위치가 “점프”됩니다.
Usually, components that rely on layout information don’t need to render on the server anyway. For example, it probably doesn’t make sense to show a Tooltip
during the initial render. It is triggered by a client interaction.
일반적으로 레이아웃 정보에 의존하는 컴포넌트는 서버에서 렌더링할 필요가 없습니다. 예를 들어, 초기 렌더링 중에 Tooltip
을 표시하는 것은 의미가 없을 수 있습니다. 이는 클라이언트 상호작용에 의해 촉발됩니다.
However, if you’re running into this problem, you have a few different options: 하지만 이 문제가 발생하는 경우 몇 가지 다른 옵션이 있습니다:
-
Replace
useLayoutEffect
withuseEffect
. This tells React that it’s okay to display the initial render result without blocking the paint (because the original HTML will become visible before your Effect runs).useLayoutEffect
를useEffect
로 바꾸세요. 이렇게 하면 React가 페인트를 막지 않고 초기 렌더링 결과를 표시해도 괜찮다고 알려줍니다(Effect가 실행되기 전에 원래 HTML이 보이게 되므로). -
Alternatively, mark your component as client-only. This tells React to replace its content up to the closest
<Suspense>
boundary with a loading fallback (for example, a spinner or a glimmer) during server rendering. 또는 컴포넌트를 클라이언트 전용으로 표시하세요. 이렇게 하면 서버 렌더링 중에 가장 가까운<Suspense>
경계까지의 콘텐츠를 로딩 폴백(예: spinner 나 glimmer)으로 대체하도록 React에 지시합니다. -
Alternatively, you can render a component with
useLayoutEffect
only after hydration. Keep a booleanisMounted
state that’s initialized tofalse
, and set it totrue
inside auseEffect
call. Your rendering logic can then be likereturn isMounted ? <RealContent /> : <FallbackContent />
. On the server and during the hydration, the user will seeFallbackContent
which should not calluseLayoutEffect
. Then React will replace it withRealContent
which runs on the client only and can includeuseLayoutEffect
calls. 또는 hydration 후에만useLayoutEffect
를 사용하여 컴포넌트를 렌더링할 수 있습니다.false
로 초기화된 불리언isMounted
state를 유지하고,useEffect
호출 내에서 이를true
로 설정합니다. 그러면 렌더링 로직은 다음과 같을 수 있습니다:return isMounted ? <RealContent /> : <FallbackContent />
서버에서 hydration이 진행되는 동안 사용자는useLayoutEffect
를 호출하지 않는FallbackContent
를 보게 됩니다. 그러면 React는 클라이언트에서만 실행되고useLayoutEffect
호출하는RealContent
로 대체합니다. -
If you synchronize your component with an external data store and rely on
useLayoutEffect
for different reasons than measuring layout, consideruseSyncExternalStore
instead which supports server rendering. 컴포넌트를 외부 데이터 저장소와 동기화하고 레이아웃 측정이 아닌 다른 이유로useLayoutEffect
에 의존하는 경우, 대신 서버 렌더링을 지원하는useSyncExternalStore
를 고려하세요.