React 컴포넌트는 순수 함수처럼 동작해야 합니다. 같은 props와 state를 받으면 항상 같은 JSX를 반환해야 하며, 렌더링 중에 외부 시스템에 영향을 주어서는 안 됩니다. 하지만 실제 애플리케이션에서는 API 호출, 타이머 설정, DOM 직접 조작처럼 외부 시스템과 상호작용해야 하는 경우가 많습니다. useEffect는 이런 Side Effect를 안전하게 처리하는 Hook입니다.
useEffect(() => {
// Side Effect 코드
return () => {
// Cleanup 코드 (선택)
};
}, [의존성]);
useEffect는 세 가지 요소로 구성됩니다. 첫 번째 인자인 Effect 함수, 이 함수가 반환하는 Cleanup 함수, 그리고 두 번째 인자인 의존성 배열입니다.
의존성 배열은 Effect가 언제 다시 실행될지를 결정합니다.
// 1. 의존성 배열 없음 — 모든 렌더링 후 실행
useEffect(() => {
console.log("렌더링될 때마다 실행");
});
// 2. 빈 배열 — 최초 마운트 시 한 번만 실행
useEffect(() => {
console.log("마운트 시 한 번만 실행");
}, []);
// 3. 값이 있는 배열 — 배열 안의 값이 변경될 때마다 실행
useEffect(() => {
console.log(`userId가 ${userId}로 변경됨`);
}, [userId]);
의존성 배열 안에는 Effect 함수 내부에서 사용하는 모든 반응형 값(state, props, 컴포넌트 내부에서 선언된 변수)을 포함해야 합니다. 빠뜨리면 오래된 값을 참조하는 버그가 발생할 수 있습니다.
반응형 값(Reactive Value): 렌더링 중에 계산되는 값. props, state, 컴포넌트 본문 안에서 선언된 변수가 해당됨. 변경되면 리렌더링을 트리거함.
Effect 함수가 함수를 반환하면, React는 해당 Effect를 다시 실행하기 전과 컴포넌트가 언마운트될 때 이 Cleanup 함수를 호출합니다.
useEffect(() => {
const intervalId = setInterval(() => {
setCount((c) => c + 1);
}, 1000);
return () => {
clearInterval(intervalId); // Cleanup: 인터벌 제거
};
}, []);
Cleanup을 빠뜨리면 컴포넌트가 언마운트된 후에도 타이머, 이벤트 리스너, 서버 구독이 계속 실행되어 메모리 누수가 발생합니다.
Mount:
렌더링 완료 → Effect 함수 실행
Update (의존성 변경 시):
리렌더링 완료 → 이전 Effect의 Cleanup 실행 → 새 Effect 함수 실행
Unmount:
컴포넌트 제거 → 마지막 Effect의 Cleanup 실행
의존성 배열에서 값 누락: Effect 내부에서 사용하는 state나 props를 의존성 배열에서 빠뜨리면 초기 렌더링 시의 값을 계속 참조하는 클로저 문제가 발생합니다.
// 버그: count가 항상 0으로 고정됨
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // 초기값 0만 참조
}, 1000);
return () => clearInterval(id);
}, []); // count가 의존성에 없음
// 수정: 함수형 업데이트로 최신 값 참조
useEffect(() => {
const id = setInterval(() => {
setCount((c) => c + 1); // 항상 최신 state 참조
}, 1000);
return () => clearInterval(id);
}, []);
렌더링 중 Side Effect 실행: useEffect 없이 컴포넌트 본문에서 직접 API를 호출하면 렌더링마다 요청이 발생합니다. Side Effect는 반드시 useEffect 안에서 실행해야 합니다.
빈 의존성 배열 남용: 빈 배열을 쓰면 마운트 시 한 번만 실행되어 편해 보이지만, Effect 내부에서 사용하는 값이 변경되어도 Effect가 다시 실행되지 않아 오래된 값을 계속 참조하게 됩니다. 의존성은 정직하게 기입해야 합니다.