← 글 목록

notes

useEffect와 useLayoutEffect.

useEffect와 useLayoutEffect의 실행 시점·용도·렌더링 차이 정리.

  • 둘 다 모양은 같고 실행 시점만 다르다
  • 화면이 한 번 튀는 게 보일 때만 useLayoutEffect 를 쓴다
  • 나머지는 전부 useEffect 로 충분하다

렌더링 순서

React 가 DOM 을 바꾼 다음(commit) useLayoutEffect 가 동기로 돌고, 그다음 브라우저가 화면을 그리고(paint), 그 뒤에 useEffect 가 돈다. 그래서 useLayoutEffect 는 사용자가 화면을 보기 전에 DOM 작업을 끝낼 수 있다.

useEffect

기본은 이쪽이다. API 호출, 이벤트 리스너 등록과 해제, 타이머와 구독, localStorage 읽기나 쓰기처럼 화면 깜빡임과 상관없는 일은 전부 여기서 한다. paint 를 막지 않아서 부담이 적고, 대신 결과가 한 프레임 늦게 보일 수 있다.

useEffect(() => {
  // 화면이 이미 그려진 뒤 실행
  fetchData();
  subscribe();
}, [deps]);

useLayoutEffect

getBoundingClientRect 나 offsetHeight 로 크기를 재고 그 값으로 위치나 스타일을 바로 고칠 때 쓴다. 툴팁, 팝오버 위치, 애니메이션 시작점, 스크롤 위치 복원 같은 것들이다. paint 전에 동기로 돌아서 그동안 화면 그리기가 막히고, 남용하면 느려진다.

useLayoutEffect(() => {
  // paint 전에 실행, 사용자는 아직 못 본 상태
  const height = ref.current.getBoundingClientRect().height;
  setHeight(height);
}, [deps]);

깜빡임 예시

같은 코드를 useEffect 에 넣으면 잠깐 잘못된 위치가 보였다가 옮겨갈 수 있다. useLayoutEffect 에 넣으면 그리기 전에 위치가 정해진다. 툴팁, 팝오버처럼 측정해서 위치를 잡는 UI 에서 차이가 난다.

// useEffect: 잠깐 잘못된 위치가 보였다가 이동할 수 있다
useEffect(() => {
  setTop(ref.current.offsetTop);
}, []);

// useLayoutEffect: paint 전에 위치 확정
useLayoutEffect(() => {
  setTop(ref.current.offsetTop);
}, []);

고르는 기준

DOM 을 재고 고치는 것 때문에 화면이 한 번 튀면 useLayoutEffect, 아니면 useEffect 다. paint 전에 꼭 해야 한다는 게 확실할 때만 layout 쪽을 꺼낸다.

자주 헷갈리는 것

데이터 fetch 는 layout 이 아니라 useEffect 다. 상태 업데이트는 둘 다 되는데, layout 에서 바꾸면 동기라서 그로 인한 다시 렌더도 paint 전에 일어난다.

Strict Mode 개발 모드에서는 둘 다 두 번 돌 수 있다. React 18 부터 의도한 동작이라 cleanup 을 제대로 짠는지 확인하는 용도다.

Next.js App Router 에서는 둘 다 'use client' 컴포넌트에서만 쓴다. 서버 렌더링 때는 둘 다 실행되지 않는다. React 18 까지는 서버에서 useLayoutEffect 경고가 떴는데 19 에서는 경고가 빠졌다. 그래도 첫 화면이 layout effect 에 기대면 서버에서 그린 HTML 과 달라 보일 수 있다.

내가 회사 프로젝트에서 useLayoutEffect 를 떠올리는 건 스크롤, Intersection Observer, 모달 위치를 잡는 코드에서 측정하자마자 스타일을 바꿔야 할 때뿐이다.