Effect의 의미
앞선 내용에서는 컴포넌트 내부의 State와 화면이 연결되는 부분만 다뤘습니다.
하지만 실제 서비스는 화면 안에서만 끝나지 않고, 반드시 화면 밖의 세계와도 연결되어야 합니다.
그 예시는 다음과 같습니다.
- 서버에서 데이터를 가져오는 일 (Fetch / API)
- 브라우저의 탭 제목 변경하기 (document.title)
- 타이머 / 알림 (setTimeout / setInterval)
- 외부 이벤트 감지하기 (키보드 및 스크롤 리스너)
- 외부 위젯과 연동하기 (지도 / 차트 등의 라이브러리)
이런 일들은 단순히 JSX를 DOM으로 바꾸는 랜더링 과정에서는 할 수 없고
React가 DOM을 실제로 업데이트하고 화면을 다시 그린 직후에 실행됩니다.
React에서는 이런 동작을 부수 효과(Effect)라고 부릅니다.
간단하게 추가 동작이나, 후속 작업을 의미하며
결론적으로 컴포넌트가 최초로 랜더링되어 DOM 업데이트된 직후 다시 업데이트가 발생해
DOM이 다시 경신된 직후 실행되는 추가 작업을 적어두는 자리입니다.
실행 시점
실제 브라우저 이벤트와 비교해 실행 시점을 예측할 경우 다음과 같습니다.

DOMContentLoad는 HTML이 파싱되어 DOM 트리가 준비되었을 때 발생하고
load는 이미지와 리소스까지 모두 불러온 뒤 실행합니다.
useEffect는 이 2가지 사이의 시점과 비슷하게 동작합니다.
즉, DOM 트리가 준비되고, React가 실제 DOM 변경 사항을 적용한 직후 실행되며
이미지 등 리소스는 기다리지 않고 바로 실행한다는 점에서 DOMContentLoaded에 가깝습니다.
구문 분석
기본형
useEffect(() => {
// 컴포넌트가 최초로 랜더링된 뒤 DOM 업데이트가 끝난 직후 실행될 코드
});먼저 공통 사항으로 useEffect() 훅을 호출하며, 안쪽 화살표 함수는 실제 실행할 작업을 담는 콜백 함수입니다.
즉, useEffect는 React가 컴포넌트를 최초로 랜더링해 DOM을 업데이트한 직후 그 뒤 다시 업데이트 후 DOM을 경신 후에
콜백 함수를 실행하라고 React에게 알려주는 문법입니다.
확장형 - cleanup 포함 구조
useEffect(() => {
// 실행할 코드
return () => {
// 정리 코드
}
})클린업은 말 그대로 청소하는 과정입니다.
예를 들어, setInterval로 타이머를 켰다면 clearInterval로 타이머를 꺼주어야 합니다.
addEventListener로 이벤트를 등록했다면 removeEventListener로 해제해야 합니다.
이처럼 한번 만든 연결을 정리해주는 것이 클린업입니다.
React는 같은 Effect가 다시 실행되기 전에 언마운트될 때 클린업 함수를 자동으로 실행해줍니다.
확장형 - 의존성 배열을 통한 실행 시점 조절
useEffect(() => {
// 실행할 코드
}, [값1, 값2, ...]);useEffect 훅의 두번째 인자로 의존성 배열을 전달합니다.
의존성 배열은 React에게 이 값이 바뀔 때만 Effect를 다시 실행하라는 신호입니다.
여기서 의존성 배열이 없으면 컴포넌트가 리 랜더링될 때마다 매번 실행하라는 의미이고,
배열이 있으면 지정한 값이 바뀔 때에만 Effect를 다시 실행합니다.
그리고, 의존성 배열이 비어있으면, 최초 랜더링 직후 한번만 실행합니다.
여기서 중요한 점은 배열에 무엇을 넣어야 하는가입니다.

단순한 지역 변수가 아니라, React에서 실제로 참조하는 props나 State 같은 반응형 값을 넣어야
React가 추적하고 값이 바뀔 때마다 Effect를 실행해줍니다.
반대로, 랜더링마다 새로 생기는 임시 변수나 지역 변수는 React가 추적하지 않으므로 의미가 없습니다.
의존성 배열 케이스 예시
두번째 인자(의존성 배열)을 아예 넣지 않았을 때 - 리 랜더링 때마다 실행
시점은 브라우저 이벤트인 DOMContentLoaded와 비슷하지만 문서 전체가 아닌 컴포넌트 단위에서 발생합니다.
따라서 컴포넌트가 리 랜더링될 때마다 DOM 업데이트 직후 Effect를 재실행합니다.
아래 예시 코드를 보겠습니다.
import { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('useEffect 실행 : 현재 count =', count)
});
return (
<div>
<p>현재 카운트: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
export default Counter;순서대로 Effect의 실행 시점을 분석하면 먼저 컴포넌트가 DOM 업데이트를 한 후 로그를 출력합니다.

그리고 버튼을 클릭해서 상태가 바뀌면 리 랜더링되므로, 다시 콘솔이 출력됩니다.

여기서 cleanup 함수를 추가한다면, 다시 리 랜더링 되어 Effect를 재실행하기 전에 cleanup 함수를 실행해 정리합니다.
예를 들어 timer 함수를 적용하면 아래와 같습니다.
useEffect(() => {
const id = setInterval(() => { console.log('타이머 동작 중'); }, 1000);
return () => { clearInterval(id); };
});이렇게 되면 Effect가 다시 실행되기 직전이나 컴포넌트가 언마운트될 때마다
이전 타이머를 정리하여 중복 실행을 방지합니다.
⚠️ 이 패턴을 실무에서 적용할 때 주의할 점은
랜더링이 잦은 컴포넌트에 API 요청 같은 무거운 요청을 넣을 경우 위험합니다.
사용자 액션마다 서버 요청이 쏟아질 수 있기 때문입니다.
👌 반대로 유용한 상황인 점은
디버깅 때 랜더링마다 값이 어떻게 변하는지 확인하거나
외부 차트 UI 처럼 DOM이 갱신될때마다 재 초기화가 필요할 경우에는 이 방식이 유용합니다.
결론적으로, 두번째 인자를 생략한 useEffect는 리 랜더링마다 실행되며, cleanup을 통해 반복 작업을 관리합니다.
두번째 인자(의존성 배열)이 빈 배열일 때 - 최초 랜더링 직후만 실행
컴포넌트가 처음 한 번만 랜더링된 직후 실행되며, 이후 다시 랜더링되어도 재실행되지 않습니다.
State나 props가 바뀔 때마다 컴포넌트가 리 랜더링되는 것은 React의 기본 동작입니다.
그러나 모든 작업이 반복될 필요는 없고, 어떤 동작은 한번만 실행하면 충분할 수 있습니다.
예를 들면 다음과 같은 경우가 있습니다.
- 사용자가 처음 페이지 들어왔을 때 환영 메시지 표시하기
- 특정 입력창에 자동 포커스 주기
- 찹업창 자동 표시 등의 초기 UI 동작하기
이는 페이지가 처음 완성될 때 딱 한번만 발생하므로 DOMContentLoaded와 매우 유사한 느낌입니다.
다만 다른 점은 그 범위가 특정 React 컴포넌트 안에서라는 점입니다.
여기서도 cleanup 함수를 함께 가질 수 있는데, 이 때는 컴포넌트가 언마운트 될때만 cleanup 함수가 실행됩니다.
원래는 effect 실행 직전마다 cleanup 함수를 호출하지만, 이 경우는 effect가 딱 1번 실행되기 때문입니다.
실무에서 사용되는 패턴의 대표적인 유형은 모달 창이나 드롭다운 메뉴에서 ESC 키로 닫기 기능을 구현할 때
모달이 열릴 때 keydown 이벤트를 등록하고, 모달이 닫힐 때 cleanup으로 이벤트를 제거하면 됩니다.
두번째 인자(의존성 배열)에 특정 값이 있을 때 - 특정 값이 변할 때마다 Effect 실행
이 패턴은 실무에서 자주 사용되는데, UI라는 것은 항상 변하는 값에 맞추어 다시 그려지고 동작해야 하기 때문입니다.
예를 들어 아래와 같은 경우가 있을 수 있습니다.
- 사용자가 검색어를 입력할 때마다 실행할 코드
- 다크 모드 혹은 라이트 모드가 바뀔 때 실행할 코드
- 토글 버튼이 켜질 때 실행할 코드
- 서버 요청 및 데이터 갱신
- 스크롤 위치 감지하기
여기서 의존성 배열은 특정 값이 바뀔때만 반응하는 일종의 감시자(Watcher) 역할을 합니다.