React 컴포넌트 생명주기 (Lifecycle)
2025. 5. 18. 20:49

React는 컴포넌트 기반 UI 라이브러리이며, 컴포넌트는 생명주기(Lifecycle)를 갖는다.

이 생명주기는 컴포넌트가 생성되고, 업데이트되며, 제거되는 일련의 과정을 의미한다.


컴포넌트 생명주기 요약

  1. 마운트(Mount): 컴포넌트가 생성되어 가상 DOM을 거쳐 실제 DOM에 추가되는 시점
  2. 업데이트(Update): props 또는 state가 변경되어 다시 렌더링되는 시점
  3. 언마운트(Unmount): 컴포넌트가 가상 DOM과 실제 DOM 양쪽에서 제거되는 시점

 

1. 마운트 (Mount)

React가 컴포넌트를 처음 렌더링할 때 실행되는 흐름:

  1. Lazy initializer (게으른 초기화)useState(() => ...): 마운트 시 1회만 실행됨
  2. Render – 컴포넌트 함수 실행 → JSX 반환 → 가상 DOM 구성
  3. React updates DOM – 가상 DOM을 실제 DOM에 반영 (커밋 단계)
  4. useLayoutEffect 실행 – DOM 반영 직후, 브라우저 렌더링 직전에 동기적으로 실행됨
  5. 브라우저 렌더링(PAINT) – 실제 DOM이 브라우저에 의해 화면에 그려지는 시점
  6. useEffect 실행 – 브라우저 렌더링 이후 비동기 실행됨
const Example = () => {
  const [count, setCount] = useState(() => {
    console.log('1. Lazy initializer 실행');
    return 0;
  });

  useLayoutEffect(() => {
    console.log('4. LayoutEffect 실행');
  }, []);

  useEffect(() => {
    console.log('6. Effect 실행');
  }, []);

  console.log('2. Render 실행');
  return <div>{count}</div>;
};

출력: 1. Lazy initializer → 2. Render 실행 → 4. LayoutEffect 실행 → 6. Effect 실행

 

2. 업데이트 (Update)

컴포넌트가 state나 props 변경으로 인해 다시 렌더링될 때 실행 순서:

업데이트 시 실행 순서 요약

  • 렌더 함수 실행 → 가상 DOM 재구성
  • React updates DOM → 변경된 가상 DOM을 실제 DOM에 반영
  • LayoutEffect cleanup → 이전 useLayoutEffect에서 등록한 정리 함수 실행
  • LayoutEffect 실행
  • 브라우저 렌더링(PAINT)
  • Effect cleanup → 이전 useEffect의 정리 함수 실행
  • Effect 실행
const Example = ({ value }) => {
  useLayoutEffect(() => {
    console.log('LayoutEffect 실행');
    return () => console.log('LayoutEffect 정리(cleanup)');
  }, [value]);

  useEffect(() => {
    console.log('Effect 실행');
    return () => console.log('Effect 정리(cleanup)');
  }, [value]);

  console.log('렌더 실행');
  return <div>{value}</div>;
};

출력 순서: 렌더 실행 → LayoutEffect cleanup → LayoutEffect 실행 → Effect cleanup → Effect 실행

 

3. 언마운트 (Unmount)

컴포넌트가 DOM에서 제거될 때 실행되는 cleanup 함수들:

  • useLayoutEffect의 cleanup 함수
  • useEffect의 cleanup 함수
const Example = () => {
  useLayoutEffect(() => {
    return () => {
      console.log('LayoutEffect 정리 (언마운트)');
    };
  }, []);

  useEffect(() => {
    return () => {
      console.log('Effect 정리 (언마운트)');
    };
  }, []);

  return <div>Unmount Example</div>;
};

예상 출력: LayoutEffect 정리 (언마운트) → Effect 정리 (언마운트)


useLayoutEffect

🔍 언제, 왜 필요한가?

useLayoutEffect는 가상 돔 계산 결과가 실제 DOM에 반영된 직후, 브라우저가 화면에 그리기 직전에 동기적으로 실행된다.

이 시점에 DOM 측정, 스타일 조정 등 사용자 눈에 보이기 전에 처리해야 하는 작업을 수행할 수 있다.

 

예를 들어, 어떤 카드 요소가 있고 이 카드에 마우스를 올리면 확장되는 박스가 나타난다고 해보자.

이 박스는 카드의 아래에 보이기를 원하지만, 아래 공간이 부족하면 위에 나타나야 한다.

 

이걸 구현하려면 다음과 같은 흐름이 필요하다:

  1. 박스를 일단 카드 아래에 렌더링한다 (이 시점에서는 위치가 정확하지 않을 수 있음)
  2. useLayoutEffect에서 박스의 실제 높이를 측정한다
  3. 만약 아래 공간이 부족하면 위로 올려서 다시 렌더링한다

 

하지만, 이런 렌더링은 모두 브라우저가 화면을 그리기 전에 완료되는것이 좋다.

그렇지 않으면 박스가 아래 언저리에서 위로 "툭" 하고 이동하는 느낌을 사용자에게 줄 수 있다.

따라서 이럴 땐 useLayoutEffect를 써서 측정과 위치 조정을 브라우저 페인팅 전에 마치도록 해야 한다.

useLayoutEffect(() => {
  const height = divRef.current.offsetHeight;
  if (height > 300) {
    divRef.current.style.fontSize = '14px';
  }
}, []);
  • 이 코드는 브라우저 렌더링 전에 실행되므로, 사용자는 바뀌는 과정을 보지 않고 결과만 본다.

 

⚠️ useEffect에서 하면?

useEffect(() => {
  const height = divRef.current.offsetHeight;
  if (height > 300) {
    divRef.current.style.fontSize = '14px';
  }
}, []);
  • 이 코드는 브라우저 렌더링이 끝난 후 실행
  • 사용자에게는 **기존 스타일이 잠깐 보였다가 바뀌는 현상(깜빡임)**이 생길 수 있음

 

⚠️ 과도한 사용에 대한 주의

useLayoutEffect는 브라우저가 화면을 그리기 전에 실행되는 동기 함수다.

이 때문에 실행 중에는 브라우저가 페인팅을 멈추고 기다려야 하며, 불필요하게 많이 사용하면 렌더링 성능이 저하될 수 있다.

  • DOM 측정이나 위치 보정이 꼭 필요한 상황에서만 최소한으로 사용해야 하며
  • 일반적인 효과나 비동기 처리는 useEffect를 사용하는 것이 성능 면에서 유리하다

참고