useEffect — 사이드 이펙트와 생명주기
이 토픽을 마치면
useEffect가 언제 실행되는지, 의존성 배열이 뭔지, 클린업 함수가 왜 필요한지 이해하게 됩니다.
사이드 이펙트란
React 컴포넌트의 주된 역할은 상태를 받아서 UI를 그리는 것입니다. 하지만 그 외의 작업도 필요합니다:
- API에서 데이터 가져오기
- 타이머 설정
- 문서 제목 변경
- 이벤트 리스너 등록
이런 작업을 **사이드 이펙트(side effect)**라고 합니다. useEffect는 이 사이드 이펙트를 처리하는 Hook입니다.
기본 사용법
javascript
import { useEffect, useState } from "react";
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
}, [userId]);
if (!user) return <p>Loading...</p>;
return <h1>{user.name}</h1>;
}useEffect는 두 개의 인자를 받습니다:
- 실행할 함수 (이펙트)
- 의존성 배열 (언제 다시 실행할지)
의존성 배열
의존성 배열에 따라 실행 시점이 달라집니다:
javascript
// 매 렌더링마다 실행 (의존성 배열 없음)
useEffect(() => {
console.log("매번 실행");
});
// 마운트 시 한 번만 실행 (빈 배열)
useEffect(() => {
console.log("처음 한 번");
}, []);
// userId가 바뀔 때마다 실행
useEffect(() => {
fetchUser(userId);
}, [userId]);React는 의존성 배열의 값이 이전 렌더링과 다른지 비교합니다. 다르면 이펙트를 다시 실행합니다.
클린업 함수
javascript
useEffect(() => {
const timer = setInterval(() => {
console.log("tick");
}, 1000);
return () => {
clearInterval(timer);
};
}, []);이펙트 함수에서 함수를 반환하면, 그게 클린업(cleanup) 함수입니다. 다음 상황에서 실행됩니다:
- 컴포넌트가 언마운트될 때 (화면에서 사라질 때)
- 이펙트가 다시 실행되기 직전 (의존성이 바뀌었을 때)
타이머 해제, 이벤트 리스너 제거, 구독 취소 같은 정리 작업에 씁니다.
javascript
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);흔한 실수: 무한 루프
javascript
// 무한 루프!
useEffect(() => {
setCount(count + 1);
}); // 의존성 배열 없음 → 매 렌더링 실행 → setState → 리렌더링 → 또 실행이펙트 안에서 상태를 변경하면 리렌더링이 일어나고, 의존성 배열이 없으면 또 이펙트가 실행됩니다.
javascript
// 올바른 방식
useEffect(() => {
fetchData().then(setData);
}, []); // 빈 배열 → 한 번만 실행핵심
useEffect는 렌더링 후에 사이드 이펙트를 실행합니다. 의존성 배열이 빈 배열이면 마운트 시 한 번, 값이 있으면 그 값이 바뀔 때 실행됩니다. 클린업 함수로 타이머, 리스너 등을 정리해야 메모리 누수를 방지합니다.