일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- next-cookies
- msw
- Database
- useLayoutEffect
- 클래스
- react-hook
- CSR
- 리액트훅
- 리액트 훅
- SSR
- reactquery
- useEffect
- ErrorBoundary
- Firebase
- NextJs
- react-hook-form
- docker
- key
- lazy()
- express
- 리액트
- react
- 모던자바스크립트
- 초기마운트
- Today
- Total
한우의 개발일기
useEffect vs useLayoutEffect 본문
useLayoutEffect vs useEffect: React 내부 동작과 실용적 차이점
React 개발자라면 useEffect
는 매우 친숙한 훅일 것입니다. 하지만 그의 사촌 격인 useLayoutEffect
는 상대적으로 덜 사용되고 이해되는 경향이 있죠. 이 두 훅은 언뜻 보기에 비슷하지만, React 내부적으로는 상당히 다른 타이밍에 실행됩니다. 이 글에서는 두 훅의 차이점, 내부 동작 원리, 그리고 언제 어떤 훅을 사용해야 하는지 알아보겠습니다.
실행 타이밍의 차이
먼저 두 훅의 가장 큰 차이점은 실행 타이밍입니다:
- useEffect: 브라우저가 화면을 그린 후에 비동기적으로 실행됩니다.
- useLayoutEffect: 브라우저가 화면을 그리기 전에 동기적으로 실행됩니다.
이 차이를 시각적으로 나타내면 다음과 같습니다:
React 렌더링 → DOM 업데이트 → useLayoutEffect 실행 → 브라우저 페인팅 → useEffect 실행
React 내부 동작 들여다보기
React의 소스 코드를 살펴보면 이 두 훅이 어떻게 다르게 처리되는지 이해할 수 있습니다. 렌더링 후 커밋 단계에서 다음과 같은 순서로 처리됩니다:
// 간소화된 React 내부 구현
function commitRootImpl(root, renderPriorityLevel) {
// 1. DOM 요소 업데이트, 참조 분리 등 변경사항을 커밋
commitMutationEffects(root, renderPriorityLevel);
// 2. Layout 효과 커밋 (useLayoutEffect 콜백 호출)
commitLayoutEffects(root, lanes);
// 브라우저는 여기서 화면을 그립니다 (paint)
// 3. 비동기적으로 Effect 스케줄링 (useEffect는 나중에 호출됨)
schedulePassiveEffects(pendingPassiveHookEffectsUnmount);
schedulePassiveEffects(pendingPassiveHookEffectsMount);
}
실제 React 내부 코드를 보면 useLayoutEffect
의 콜백은 commitLayoutEffectOnFiber
함수 내에서 직접 호출되는 반면, useEffect
의 콜백은 schedulePassiveEffects
를 통해 나중에 실행되도록 스케줄링됩니다.
간단한 예제로 차이점 이해하기
다음 예제를 통해 두 훅의 동작 차이를 확인해 봅시다:
function Example() {
const [count, setCount] = useState(0);
const divRef = useRef(null);
// 버전 1: useEffect 사용
useEffect(() => {
if (divRef.current) {
divRef.current.style.color = 'red';
divRef.current.style.fontSize = '24px';
console.log('useEffect 실행');
}
}, [count]);
/*
// 버전 2: useLayoutEffect 사용
useLayoutEffect(() => {
if (divRef.current) {
divRef.current.style.color = 'red';
divRef.current.style.fontSize = '24px';
console.log('useLayoutEffect 실행');
}
}, [count]);
*/
return (
<div>
<div ref={divRef}>{count}</div>
<button onClick={() => setCount(count + 1)}>증가</button>
</div>
);
}
useEffect로 스타일 변경 시:
사용자는 잠깐 동안 원래 스타일이 적용된 화면을 볼 수 있습니다. 그 후에 DOM이 업데이트되어 글자 색상과 크기가 변경됩니다. 이것은 브라우저가 먼저 화면을 그린 후에 useEffect
내부의 코드가 실행되기 때문입니다.
useLayoutEffect로 스타일 변경 시:
사용자는 항상 업데이트된 스타일만 보게 됩니다. 잠깐의 깜빡임도 발생하지 않습니다. 이것은 브라우저가 화면을 그리기 전에 useLayoutEffect
내부의 코드가 이미 실행되었기 때문입니다.
성능 고려사항
두 훅을 선택할 때 고려해야 할 중요한 성능 측면이 있습니다:
- useLayoutEffect는 브라우저의 페인팅을 차단합니다. 따라서 내부에 무거운 연산이 있으면 화면 업데이트가 지연될 수 있습니다.
- useEffect는 화면 업데이트를 차단하지 않아 사용자 경험이 더 부드럽습니다.
이런 이유로 React 팀은 특별한 이유가 없다면 useEffect
를 사용할 것을 권장합니다.
실제 사용 사례
useLayoutEffect가 적합한 경우:
- DOM 측정이 필요할 때
function MeasureExample() {
const [width, setWidth] = useState(0);
const divRef = useRef(null);
useLayoutEffect(() => {
if (divRef.current) {
// 요소의 크기를 측정하여 상태 업데이트
const divWidth = divRef.current.getBoundingClientRect().width;
setWidth(divWidth);
}
}, []);
return (
<div>
<div ref={divRef}>내용</div>
<p>측정된 너비: {width}px</p>
</div>
);
}
이 경우 useEffect
를 사용하면 사용자가 잠깐 동안 width
가 0인 상태를 볼 수 있으며, 이는 바람직하지 않습니다.
- DOM 기반 애니메이션
function AnimationExample() {
const boxRef = useRef(null);
useLayoutEffect(() => {
if (boxRef.current) {
// 시작 위치 설정
boxRef.current.style.transform = 'translateX(0px)';
// 애니메이션 시작
requestAnimationFrame(() => {
boxRef.current.style.transition = 'transform 1s ease';
boxRef.current.style.transform = 'translateX(500px)';
});
}
}, []);
return <div ref={boxRef} className="box" />;
}
useEffect
를 사용하면 박스가 시작 위치에서 잠깐 깜빡인 후 애니메이션이 시작될 수 있습니다.
- 툴팁이나 모달 위치 조정
function Tooltip({ targetRef, content }) {
const tooltipRef = useRef(null);
useLayoutEffect(() => {
if (tooltipRef.current && targetRef.current) {
const targetRect = targetRef.current.getBoundingClientRect();
// 타겟 요소 위에 툴팁 위치시키기
tooltipRef.current.style.top = `${targetRect.top - tooltipRef.current.offsetHeight}px`;
tooltipRef.current.style.left = `${targetRect.left + targetRect.width / 2 - tooltipRef.current.offsetWidth / 2}px`;
}
}, [targetRef.current]);
return <div ref={tooltipRef} className="tooltip">{content}</div>;
}
useEffect가 적합한 경우:
- 외부 API 호출
function DataFetcher({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
const fetchUser = async () => {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
};
fetchUser();
}, [userId]);
return <div>{user ? user.name : '로딩 중...'}</div>;
}
- 구독 설정 및 해제
function EventListener() {
useEffect(() => {
const handleResize = () => {
console.log('창 크기가 변경되었습니다');
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return <div>창 크기를 변경해보세요</div>;
}
- 타이머 설정
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
return <div>카운트: {count}</div>;
}
useLayoutEffect와 SSR(서버 사이드 렌더링)
useLayoutEffect
를 사용할 때 고려해야 할 또 다른 측면은 서버 사이드 렌더링(SSR)입니다. SSR 환경에서 useLayoutEffect
는 브라우저 DOM이 없기 때문에 예상대로 작동하지 않습니다. 이 경우 다음과 같은 경고를 보게 됩니다:
Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client.
SSR을 사용하는 경우 useEffect
를 사용하거나, 다음과 같은 패턴을 고려할 수 있습니다:
const useIsomorphicLayoutEffect = typeof window !== 'undefined'
? useLayoutEffect
: useEffect;
결론
useEffect
와 useLayoutEffect
의 차이를 이해하는 것은 React 애플리케이션의 성능과 사용자 경험을 최적화하는 데 중요합니다. 요약하자면:
- useEffect: 화면 업데이트 후 비동기적으로 실행됩니다. 대부분의 경우 이 훅을 사용하세요.
- useLayoutEffect: 화면 업데이트 전 동기적으로 실행됩니다. DOM 측정이나 시각적 깜빡임 방지가 필요할 때 사용하세요.
React의 내부 동작 원리를 이해하면 이 두 훅을 더 효과적으로 활용할 수 있으며, 각각의 적절한 사용 사례를 식별하는 데 도움이 됩니다. 항상 성능 영향을 고려하고, 특별한 이유가 없다면 기본적으로 useEffect
를 선택하는 것이 좋습니다.
React 앱을 더 효율적으로 구축하는 데 이 지식이 도움이 되길 바랍니다!
'React' 카테고리의 다른 글
Suspense 알아보기 (1) | 2025.03.24 |
---|---|
Errorboundary 내부동작 이해하기 (1) | 2025.03.23 |
useAsync 파해치기 (0) | 2025.03.18 |
useRef() 파해치기 (0) | 2025.03.18 |
리액트의 초기 마운트 (0) | 2025.02.13 |