함정
useLayoutEffect는 성능 저하를 유발할 수 있어요. 가능하다면 useEffect를 사용하세요.
useLayoutEffect
는 useEffect
의 한 종류로 브라우저가 화면을 리페인트하기 전에 실행돼요.
useLayoutEffect(setup, dependencies?)
Reference | 레퍼런스
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);
}, []);
// ...
}
Parameters | 파라미터(매개변수)
setup
: 이펙트의 로직을 가진 함수. setup 함수는 선택적으로 cleanup 함수를 반환할 수도 있어요. 어떤 레이아웃 이펙트가 실행되기 전에 컴포넌트가 DOM에 추가되면 리액트는 setup 함수를 실행할 거예요. 변화된 의존성으로 인한 모든 리렌더링 후에 리액트는 (값을 제공받았다면) 옛날 값으로 cleanup 함수를 먼저 실행하고, 그 다음에 새 값을 setup 함수를 실행해요. 컴포넌트가 DOM에서 제거되기 전에 리액트는 cleanup 함수를 실행해요.dependencies
(선택적) :setup
코드의 내부에서 참조하는 반응값의 목록. 반응값은 props, 상태, 그리고 컴포넌트 바디 내부에서 선언된 모든 변수와 함수를 포함해요. 만약 린터가 리액트에 맞게 구성되어 있다면, 모든 반응 값이 올바르게 의존성으로 지정되어 있는지를 확인해요. 의존성 목록은 일정한 숫자의 아이템을 가지고 있어야하고[dep1, dep2, dep3]
과 같이 인라인으로 작성해야해요. 리액트는 각 의존성을Object.js
의 비교 알고리즘을 사용하여 이전의 값과 비교해요. 만약 이 인자를 생략한다면 해당 이펙트는 컴포넌트의 모든 리렌더링 후에 재실행 될 거예요.
Returns | 반환값
useLayoutEffect
는 undefined
를 반환해요.
Caveats | 주의사항
useLayoutEffect
는 훅이기 때문에 최상위 컴포넌트 또는 직접 만든 훅에서만 호출할 수 있어요. 반복문이나 조건문 안에서는 호출할 수 없어요. 만약 그럴 필요가 있다면, 컴포넌트를 추출하고 그 안으로 이펙트를 옮기세요.- 엄격한 모드(Strict Mode)가 실행 중이라면 리액트는 첫 번째 실제 setup 실행 이전에 추가적인 개발 전용 setup+cleanup 사이클을 실행해요. 이는 cleanup 로직이 setup 로직을 똑같이 따르고 있는지 그리고 setup 함수가 실행하는 무슨 작업이든 중단하거나 되돌릴 수 있는지를 확인하는 강도 테스트에요. 만약 여기서 문제가 발생한다면 cleanup 함수를 구현하세요.
- 만약 의존성 중 컴포넌트 내부에서 정의된 객체나 함수가 있다면, 이펙트를 필요한 것보다 더 자주 재실행시킬 위험이 있어요. 이를 고치기 위해서는 불필요한 객체나 함수 의존성들을 삭제하세요. 또한 상태 업데이트를 추출하고 반응적이지 않은 로직을 이펙트 밖으로 옮기세요.
- 이펙트는 클라이언트 사이드에서만 작동해요. 서버 렌더링 동안은 작동하지 않아요.
useLayoutEffect
안의 코드와useLayoutEffect
로부터 예약된 모든 상태 업데이트는 브라우저가 화면을 리페인팅하지 못하게 막아요. 만약 이를 과도하게 사용하면 어플을 느리게 만들어요. 가능하다면,useEffect
를 사용하세요.
Usage | 용법
Measuring layout before the browser repaints the screen | 브라우저가 화면을 리페인트 하기 전에 레이아웃 측정하기
대부분의 컴포넌트는 무엇을 렌더링할 것인지를 결정하기 위해 화면에서의 위치와 크기를 알 필요가 없어요. 그들은 단지 JSX만을 반환해요. 그리고나면 브라우저는 그들의 레이아웃(위치와 크기)를 결정하고 화면에 리페인트해요.
때때로 이것만으로는 충분하지 않아요. 어떤 엘리먼트가 호버되어야 나타나는 툴팁이 있다고 생각해보세요. 만약 충분한 공간이 없다면 툴팁은 엘리먼트 위에 나타나야 하지만 만약 이 또한 맞지 않는다면 아래에 나타나야 해요. 툴팁을 맞는 최종 위치에 렌더링하기 위해서는 높이를 알아야만해요. (예. 가장 위에서 잘 맞는지)
이를 수행하려면 두 번의 패스로 렌더링해야해요.
- (설령 틀린 위치라도) 아무데서나 툴팁을 렌더링한다.
- 높이를 측정하고 툴팁을 어디에 두어야할지 결정한다.
- 맞는 위치에 툴팁을 다시 렌더링한다.
이 모든 과정은 브라우저가 화면을 리페인트하기 전에 일어나야해요. 사용자에게 툴팁이 이동하는 것을 보여주고 싶지 않을 거예요. 브라우저가 화면을 리페인트하기 전에 레이아웃을 측정하려면 useLayoutEffect
를 호출하세요.
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); // 아직 실제 높이는 몰라요.
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // 실제 높이를 알고 있으니 이제 리렌더링 해요.
}, []);
// ... 렌더링 로직 안의 tooltipHeight를 이 아래에서 사용하세요 ...
}
이 작업이 진행되는 과정을 단계별로 알아보아요.
Tooltip
은 초기tooltipHeight = 0
으로 렌더링돼요. (그래서 툴팁은 잘못된 위치에 있어요.)- 리액트는 DOM 안에서 그것을 위치시키고
useLayoutEffect
안의 코드를 실행해요. useLayoutEffect
는 툴팁 콘텐츠의 높이를 측정하고 즉시 리렌더링해요.Tooltip
은 실제tooltipHeight
로 다시 렌더링해요. (그래서 툴팁은 알맞은 위치에 있어요.)- 리액트는 DOM 안에서 툴팁을 업데이트하고 브라우저는 마침내 툴팁을 보여줘요.
아래 버튼을 호버하고 툴팁이 맞는 곳으로 위치를 어떻게 조정하는지 확인하세요.
Tooltip
컴포넌트가 두번의 패스(0으로 초기화된 tooltipHeight
와 실제 측정된 높이)로 렌더링되어야함에도 불구하고 마지막 결과만을 볼 수 있다는 사실을 기억하세요. 이것이 바로 이 예시에서 useEffect
대신에 useLayoutEffct
를 사용해야하는 이유에요. 아래에서 더 자세한 차이를 확인하세요.
useLayoutEffct vs useEffect
useLayoutEffect
는 브라우저가 리페인팅을 못하게 막아요.
리액트는 useLayoutEffect
안의 코드와 useLayoutEffect
로부터 예약된 모든 상태 업데이트가 브라우저가 화면을 리페인팅하기 전에 실행될 것임을 보장해요. 이것이 툴팁을 렌더링하고, 측정하고, 유저가 첫번째 추가 렌더링을 알아차리지 못한 채로 툴팁을 다시 리렌더링하도록 만들어요. 다른 말로, useLayoutEffect
는 브라우저가 화면을 렌더링하는 것을 막아요.
useEffect
는 브라우저를 막지 않아요.
같은 예시지만 useLayoutEffect
대신 useEffect
를 사용했어요. 만약 더 느린 기기를 사용한다면 때때로 툴팁이 "깜빡거리는" 것을 볼 수도 있어요. 그리고 맞는 위치 전에 초기 위치를 짧게 볼 수 있어요.
버그를 더 쉽게 재현하기 위해 이 버전은 렌더링동안 인위적인 딜레이를 추가했어요. 리액트는 브라우저가 useEffect
내부에서 상태 업데이트를 처리하기 전에 스크린을 페인팅하도록 해요. 결론적으로 툴팁은 깜빡거리죠.
이 예시를 useLayoutEffect
로 수정하고 렌더링이 느려지는 동안에도 페인팅을 막는 것을 관찰하세요.
NOTE
두 패스로 렌더링하고 브라우저를 막는 것은 성능을 저하시켜요. 할 수 있다면 피하세요.
Troubleshooting | 트러블슈팅
I’m getting an error: “useLayoutEffect does nothing on the server” | "useLayoutEffect
가 서버에서 아무것도 안해요"라는 에러가 떠요.
useLayoutEffect
의 목적은 컴포넌트가 렌더링을 위해 레이아웃 정보를 사용하도록 하는 것이에요.
- 초기 콘텐츠를 렌더링해요.
- 브라우저가 화면을 리페인트 하기 전에 레이아웃을 측정해요.
- 읽은 레이아웃 정보를 사용하여 최종 콘텐츠를 렌더링해요.
당신과 당신이 사용하는 프레임워크가 서버 렌더링을 사용한다면 리액트 앱은 초기 렌더링 때 서버에서 HTML을 렌더링해요. 이는 자바스크립트 코드가 로드되기 전에 초기 HTML을 보여줘요.
문제는 서버에는 레이아웃 정보가 없다는 것이에요.
앞의 예시에서 Tooltip
컴포넌트 안에서 호출된 useLayoutEffect
는 콘텐츠의 높이에 맞춰서 (콘텐츠의 위 또는 아래 중) 알아서 맞는 위치에 놓도록 만들어요. 만약 최초 서버 HTML 중 일부로 Tooltip
을 렌더링한다면 이를 결정하는 것은 불가능해요. 서버에서는 아직 레이아웃이 없으니까요! 그래서 서버에서 컴포넌트를 렌더링 한다고 하더라도, 위치는 클라이언트 사이드에서 자바스크립트가 로딩되고 실행된 후에 "이동"할 거예요.
보통 레이아웃 정보에 의존하는 컴포넌트는 어쨌든 서버에서 렌더링하지 않아요. 예를 들어, 초기 렌더링동안 Tooltip
을 보여주는 것은 말이 되지 않을 거예요. 이는 클리어언트와의 상호작용으로 유발되니까요.
그러나 만약 이 문제에 직면하고 있다면 몇가지 다른 옵션이 있어요.
useLayoutEffect
를useEffect
로 대체해요. 이는 리액트에게 페인팅을 막지 않고 초기 렌더링 결과를 보여주어도 된다고 말하는 거예요. (왜냐하면 기존의 HTML은 이펙트가 발생하기 전에 보여질 거니까요.)- 대안으로, 컴포넌트를 클라이언트에서만 작동한다고 명시하세요. 이는 리액트에게 서버 렌더링 동안 컴포넌트의 콘텐츠를 (이를 테면 스피너나 반짝임 같은) 로딩 fallback과 함께 가장 가까운
<Suspense>
바운더리로 올려준다고 알려줘요. - 대안으로, 하이드레이션 이후에
useLayoutEffect
컴포넌트를 렌더링 할 수 있어요.false
로 초기화된isMounted
라는 불리안 타입의 상태를 만드세요. 그리고 그것을useEffect
호출 안에서true
로 설정하세요. 렌더링 로직은return isMounted ? <RealContent /> : <FallbackContent />
와 같을 수 있어요. 서버에서 그리고 하이드레이션동안 사용자는useLayoutEffect
를 호출하지 않은FallbackContent
를 볼 수 있어요. 그리고나서 리액트는 클라이언트 사이드에서만 실행하고useLayoutEffect
호출을 포함하는RealContent
로 대체해요. - 만약 외부 데이터 저장소와 컴포넌트를 동기화하거나 레이아웃을 측정하는 것보다 더 많은 이유로 인해
useLayoutEffect
에 의존한다면, 서버 렌더링을 지원하는useSyncExternalStore
을 사용하는 것을 고려해보세요.
'리액트 공식문서 | React Docs > Reference > react@18.2.0' 카테고리의 다른 글
[Hooks] useOptimistic | useOptimistic 훅 (0) | 2024.02.03 |
---|---|
[Hooks] useMemo | useMemo 훅 (0) | 2024.02.01 |
[Hooks] useInsertionEffect | useInsertionEffect 훅 (0) | 2024.01.25 |
[Hooks] useImperativeHandle | useImperativeHandle훅 (1) | 2024.01.21 |
[Hooks] useId | useId 훅 (0) | 2024.01.21 |