
React에서 렌더링 후 어떤 작업을 수행할 때 우리는 흔히 useEffect를 사용한다.
하지만 동일한 문법, 다른 타이밍으로 작동하는 useLayoutEffect도 존재한다.
그저 "하나는 비동기, 하나는 동기"라고 외워두기보다는,
이 둘이 왜 분리되었는지,
리액트의 렌더링 구조상 어떤 목적을 위한 것인지
그리고 우리가 언제 어떤 것을 선택해야 하는지를 구조적으로 이해해보자.
리액트는 기본적으로 렌더링 결과를 화면에 출력하는 일을 브라우저에게 위임한다.
즉, 컴포넌트 함수가 실행되면 가상 DOM이 만들어지고,
브라우저는 이걸 실제 DOM으로 패치하고, 화면을 그린다.
그런데 여기서 문제가 생긴다.
"렌더링 후 어떤 작업을 해야 할 때, 정확히 언제 해야 할까?"
예를 들어:
이처럼 렌더링 이후의 타이밍은 두 종류로 나뉘는 작업의 본질을 반영한다.
그래서 React는 이 후처리 작업을 위해 두 개의 훅을 제공한다:
useEffect → 렌더링 후, 브라우저 페인트가 끝난 뒤 실행 (비동기)useLayoutEffect → 렌더링 후, DOM이 완성되었지만 브라우저가 아직 그리기 전에 실행 (동기)| 단계 | 설명 | 관련 훅 |
|---|---|---|
| 1. 컴포넌트 함수 실행 | 리렌더링 준비 | - |
| 2. 가상 DOM 생성 | JSX → ReactElement → 가상 DOM | - |
| 3. 실제 DOM 업데이트 | 브라우저에게 실제 DOM 변경 명령 | - |
| 4. useLayoutEffect 실행 | DOM 완성됨, 브라우저가 그리기 직전 | ✅ |
| 5. 브라우저가 화면 페인팅 | 사용자에게 화면 보임 | - |
| 6. useEffect 실행 | 렌더링이 끝난 후 (비동기) | ✅ |
즉,
useLayoutEffect는 DOM을 변경할 수 있는 마지막 기회 useEffect는 화면이 사용자에게 보인 후 처리하면 되는 일들useEffect에 적합한 작업useEffect(() => {
fetchData().then(setData);
}, []);
useLayoutEffect에 적합한 작업useLayoutEffect(() => {
const height = ref.current.offsetHeight;
setHeight(height); // setState -> 즉시 DOM 반영된다.
},[]);
useLayoutEffect는 동기적으로 실행된다.
즉, 이 안에서 오래 걸리는 작업을 하면 렌더링이 차단되고,
UI가 멈춘 듯한 느낌을 줄 수 있다.
useLayoutEffect(() => {
whie (true) {} // 브라우저 멈춤
}, []);
그래서 일반적으로 항상 useEffect를 먼저 쓰고,
"시각적으로 깜빡임이나 DOM 위치 오류"가 있을 때만
useLayoutEffect로 바꾸는 방식이 추천된다.
// 깜빡임 있는 경우
useEffect(() => {
const h = ref.current.offsetHeight;
setTop(h);
},[]);
// 깜빡임 없는 경우
useLayoutEffect(() => {
const h = ref.current.offsetHeight;
setTop(h);
},[]);
useEffect와 useLayoutEffect의 차이는
단순히 "빠르냐 느리냐"가 아니라,
렌더링 이후 작업을 '어느 타이밍에서 통제할 것이냐'의 문제다.
렌더링 사이클을 정확히 이해하고,
우리가 통제하고자 하는 대상(DOM 구조인지, 시각 결과인지)을 기준으로
적절한 훅을 선택하는 것이 진짜 실력이다.