useEffect vs useLayoutEffect, 둘 다 똑같아 보이는데 뭐가 다르죠? 🤔

React 개발하다 보면 useEffect 훅은 정말 자주 만나게 되죠? 그런데 가끔 useLayoutEffect라는 이름도 비슷한 훅을 보게 돼요. 둘 다 뭔가 '이펙트(effect)'를 다루는 것 같은데... 도대체 뭐가 다르고 언제 어떤 걸 써야 할까요? 🤔

오늘은 이 두 훅의 결정적인 차이점과 언제 사용하면 좋은지, 그리고 주의할 점은 없는지 한번 쉽고 재미있게 파헤쳐 볼게요! 😉

일단 기본부터! useEffect

useEffect는 React 컴포넌트에서 사이드 이펙트(side effect)를 처리할 때 가장 흔하게 사용하는 훅이에요. 사이드 이펙트가 뭐냐고요? 컴포넌트가 렌더링되는 주된 작업 외에 부가적으로 해야 하는 일들을 말해요. 예를 들면 이런 것들이죠.

  • 서버에서 데이터 가져오기 (API 호출)

  • 이벤트 리스너 등록하거나 해제하기

  • 타이머 설정하거나 해제하기 (setTimeout, setInterval)

  • DOM을 직접 조작하는 작업 (권장되진 않지만 가끔 필요할 때가 있죠)

useEffect의 가장 큰 특징은 실행되는 타이밍이에요. 얘는 컴포넌트 렌더링이 끝나고, 화면에 변경 사항이 모두 그려진 후에 (after paint), 나중에 (비동기적으로) 슬쩍 실행된답니다. 즉, 브라우저가 화면 그리는 걸 막지 않아요.

예시) 카운터 값으로 문서 제목 바꾸기

버튼을 누르면 숫자가 올라가고, 그 숫자를 웹 페이지 제목에 표시하는 간단한 카운터예요.

import React, { useState, useEffect } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  // useEffect 사용!
  // count 값이 바뀔 때마다 실행돼요.
  useEffect(() => {
    // 렌더링이랑 화면 업데이트가 끝난 후에 여기가 실행돼요.
    console.log('useEffect 실행! 화면 업데이트 완료!');
    document.title = `카운트 ${count}`; // 웹 페이지 제목 변경
  }, [count]); // count가 바뀔 때만 이 효과를 다시 실행하라고 알려줘요.

  console.log('렌더링...'); // 렌더링은 먼저 일어나요.

  return (
    <div>
      <p>카운트 {count}</p>
      <button onClick={() => setCount(count + 1)}>증가</button>
    </div>
  );
}

버튼을 누르면 숫자가 화면에 먼저 착! 업데이트되고, 그 다음에 useEffect 안의 코드가 실행돼서 웹 페이지 제목이 바뀌는 거죠. 대부분의 사이드 이펙트는 이렇게 화면이 다 그려진 후에 처리해도 괜찮아요.

그럼 useLayoutEffect는 언제 쓰는 거죠?

useLayoutEffectuseEffect와 거의 똑같이 생겼고 하는 일도 비슷해요. 하지만 결정적인 차이가 바로 실행 타이밍에 있어요!

useLayoutEffect는 컴포넌트 렌더링이 끝나고 DOM 업데이트까지는 완료됐지만, 브라우저가 변경된 내용을 화면에 실제로 그리기 직전에! 동기적으로! 딱 실행돼요. 😮

이게 왜 중요하냐면요, 화면에 뭔가 그려지기 전에 DOM을 직접 측정하거나 스타일을 변경해야 할 때 유용하기 때문이에요. 예를 들어, 화면에 뭔가 나타났다가 스타일 변경 때문에 잠시 깜빡! 하고 바뀌는 걸 막고 싶을 때 사용할 수 있죠. 주로 레이아웃(layout)과 관련된 작업을 할 때 적합해요.

예시) 컴포넌트가 보일 때 스크롤 맨 위로 이동시키기

어떤 컴포넌트가 화면에 나타날 때 무조건 스크롤 위치를 맨 위로 보내고 싶다고 해봐요.

import React, { useLayoutEffect } from 'react';

function ScrollToTop() {
  // useLayoutEffect 사용!
  // 컴포넌트가 DOM에 추가되고 화면에 그려지기 직전에 실행돼요.
  useLayoutEffect(() => {
    console.log('useLayoutEffect 실행! 화면 그리기 직전!');
    window.scrollTo(0, 0); // 스크롤 위치를 맨 위(0, 0)로 이동
  }, []); // 빈 배열은 마운트될 때 딱 한 번만 실행하라는 의미예요.

  return <div>이 컴포넌트가 보이면 스크롤이 맨 위로 가요!</div>;
}

만약 이걸 useEffect로 했다면, 컴포넌트가 일단 화면 어딘가에 그려지고 (스크롤이 아래에 있었다면 그 위치에), 그 후에 스크롤이 맨 위로 슝! 하고 올라가는 게 보일 수도 있어요 (깜빡임). 하지만 useLayoutEffect를 쓰면 화면에 그려지기 전에 스크롤 위치를 바꿔버리니까 사용자는 처음부터 맨 위에서 보게 되는 거죠.

⚠️ 잠깐! 이것만은 주의하세요!

비슷해 보이지만 중요한 차이가 있는 만큼, 주의할 점도 있어요.

  • useLayoutEffect의 화면 블로킹 가능성
    useLayoutEffect는 동기적으로 실행되고, 이게 끝날 때까지 브라우저는 화면 그리기를 기다려요. 만약 이 안에서 시간이 오래 걸리는 무거운 작업을 하면? 화면이 멈춘 것처럼 보이거나 버벅거릴 수 있어요! 🥶 성능이 중요하다면 웬만하면 useEffect를 쓰는 것이 좋아요. useLayoutEffect는 정말 화면 그리기 전에 꼭 처리해야만 하는 레이아웃 관련 작업에만 신중하게 사용하세요!

  • 의존성 배열은 둘 다 중요해요!
    useEffectuseLayoutEffect 둘 다 두 번째 인자로 의존성 배열([])을 받죠? 이 배열 안에 넣어준 값들이 변경될 때만 훅 안의 코드가 다시 실행돼요. 이걸 제대로 지정해주지 않으면 원하지 않을 때 계속 실행되거나, 반대로 꼭 실행되어야 할 때 실행되지 않는 버그가 생길 수 있어요. 빈 배열([])을 넣으면 컴포넌트가 처음 마운트될 때 딱 한 번만 실행되고요. 의존성 배열, 정말 정말 중요하답니다! ⭐

🤔 그래서 결론은 뭐죠? 뭘 써야 할까요?

간단하게 정리해 볼게요!

  • useEffect (기본 & 추천 👍)

    • 언제 실행? 렌더링 + 화면 그리기 끝난 후 (비동기)

    • 언제 사용? 대부분의 경우! (API 호출, 이벤트 리스너, 타이머 등)

    • 장점? 화면 그리는 걸 막지 않아서 성능에 유리해요.

  • useLayoutEffect (특별한 경우 🤔)

    • 언제 실행? 렌더링 후 + 화면 그리기 직전 (동기)

    • 언제 사용? DOM 측정/변경 후 화면 업데이트가 깜빡임 없이 바로 반영되어야 할 때 (주로 레이아웃 관련)

    • 주의점? 무거운 작업 시 화면이 멈춰 보일 수 있어요.

결론적인 조언은요?
"일단 useEffect를 사용하세요! 그런데 혹시 화면이 업데이트될 때 뭔가 깜빡거리거나 레이아웃이 틀어지는 게 눈에 보인다면, 그때 useLayoutEffect를 조심스럽게 고려해보세요." 😊

0개의 댓글

Powered by GraphCDN, the GraphQL CDN