본문 바로가기
2주차

useEffect는 왜 자꾸 실행될까?

by mmhs 2025. 4. 16.

안녕하세요 YB 문혜성입니다!

 

이번에 React의 리렌더링에 대해 더 찾아보다가 자연스럽게 useEffect에 대한 궁금증이 생겼고, 그 과정에서 이 주제를 다뤄보고 싶다는 생각이 들었습니다.

또한, 세미나에서 Virtual DOM과 리렌더링 구조를 배우며 단순히 Hook 문법을 외우는 것보다 React의 작동 원리를 이해하는 것이 더 중요하다는 걸 느꼈어요.

 

따라서 이번 글에서는 useEffect가 언제 실행되는지, 왜 반복되는지, 그리고 어떻게 올바르게 사용하는지를 정리해보려고 합니다😊 


 

🧠 Virtual DOM과 리렌더링 구조

 

React는 Virtual DOM을 사용하여 효율적인 렌더링을 구현합니다. 상태가 변경되면 새로운 Virtual DOM이 생성되고, 이전 Virtual DOM과 비교하여 변경된 부분만 실제 DOM에 반영됩니다.​

 

이 다이어그램은 Virtual DOM의 구조와 실제 DOM과의 관계를 보여줍니다. 상태 변경 시 새로운 Virtual DOM이 생성되고, 이전 Virtual DOM과의 차이를 비교하여 변경된 부분만 실제 DOM에 반영하는 과정을 시각적으로 이해할 수 있습니다


🔍 React의 렌더링 구조 간단 요약

 

우리가 상태(state)를 바꾸면 React는 다음 순서로 화면을 업데이트해요.

<img src="스크린샷1.png" alt="Virtual DOM 리렌더링 설명" />

  1. 상태(state)가 변경되면
  2. 새로운 Virtual DOM이 생성되고
  3. 이전 Virtual DOM과 비교해 바뀐 부분만 찾아
  4. 실제 DOM에 반영해 렌더링합니다

이 과정을 최적화하기 위해 React는 Render PhaseCommit Phase라는 두 단계로 렌더링을 나눠요.

  • Render Phase: 어떤 부분이 바뀌었는지 계산
  • Commit Phase: 실제로 브라우저에 반영

이제 여기서 중요한 건… 바로 useEffect는 렌더링 "이후"에 실행된다는 거예요.


📌 useEffect란?

useEffect는 **React 함수형 컴포넌트에서 side effect(부수 효과)**를 처리할 때 사용하는 Hook이에요.
이벤트 리스너 등록, 타이머 설정, API 호출처럼 컴포넌트가 렌더링된 후에 실행되어야 하는 작업을 담당합니다.

 

 

이 다이어그램은 React 컴포넌트의 생명주기에서 useEffect가 언제 실행되는지를 보여줍니다. 렌더링 후에 사이드 이펙트가 실행되며, 의존성 배열에 따라 실행 여부가 결정돼요!

 


 

⚠️ 흔한 실수: 무한 렌더링

 

useEffect 내부에서 상태를 변경하면 무한 렌더링이 발생할 수 있습니다. 이를 방지하기 위해 의존성 배열을 적절히 설정해야 합니다.​

 

❌ 잘못된 예시

import { useEffect, useState } from 'react';

function CountInputChanges() {
  const [value, setValue] = useState('');
  const [count, setCount] = useState(-1);

  useEffect(() => setCount(count + 1));

  const onChange = ({ target }) => setValue(target.value);

  return (
    <div>
      <input type="text" value={value} onChange={onChange} />
      <div>Number of changes: {count}</div>
    </div>
  )
}

 

위의 코드는 변경이 의존성 배열에 포함되지 않아 무한 루프가 발생하는 예시를 보여줍니다. 이를 해결하기 위해서는 어떻게 해야할까요?

 

✅ 올바른 예시

import { useEffect, useState } from 'react';

function CountInputChanges() {
  const [value, setValue] = useState('');
  const [count, setCount] = useState(-1);

  useEffect(() => setCount(count + 1), [value]);

  const onChange = ({ target }) => setValue(target.value);

  return (
    <div>
      <input type="text" value={value} onChange={onChange} />
      <div>Number of changes: {count}</div>
    </div>
  );
}

 

이렇게 [value]를 의존성 배열로 설정하면, value가 변경될 때만 useEffect가 실행되어 무한 렌더링을 방지할 수 있어요!

 


💡 실전에서 기억할 팁

 

 

  • useEffect는 렌더링이 끝난 후 실행
  • 의존성 배열을 정확히 설정해야 불필요한 재실행을 방지할 수 있음
  • 상태 변경 함수(setState)를 useEffect 내부에 둘 땐 반드시 조건문으로 제어하거나 의존성 배열을 적절히 조정
  • 렌더링 구조 (Virtual DOM → diff → Commit)와 useEffect 실행 흐름을 연결해서 이해하면 실수가 줄어듦

 


💭 마무리하며

 

이번 세미나에서 배운 Virtual DOM 개념과 렌더링 구조는 단순히 성능 최적화의 개념이 아니라, useEffect와 같은 Hook을 정확히 이해하기 위한 핵심 기반이라는 걸 느꼈어요.

React는 렌더링을 효율적으로 해주지만, 렌더링 시점과 그 이후 실행되는 로직을 잘 구분해서 작성하는 건 개발자의 몫이에요.

혹시 여러분도 useEffect를 사용할 때 헷갈렸던 경험이 있으셨다면, 이 글이 작은 도움이라도 되었기를 바랍니다. 🙌

 

 

 

 

출처: https://blog.kohinoornimes.tech/react-fundamentals, https://github.com/donavon/hook-flow, How to Solve the Infinite Loop of React.useEffect()