
React 함수형 컴포넌트는 깔끔하지만, UI 외의 작업(예: API 호출, 이벤트 등록, 타이머 설정 등)을 어디서 처리해야 할까? 클래스 컴포넌트에서는 componentDidMount, componentDidUpdate, componentWillUnmount 등을 사용했지만, 함수형 컴포넌트에서는 useEffect Hook으로 이를 모두 처리할 수 있다.
이 글에서는 useEffect의 기본 개념부터 동작 원리, 그리고 실무에서 자주 마주치는 패턴까지 차근차근 알아본다.
useEffect는 컴포넌트가 렌더링된 이후에 부수 효과 를 수행할 수 있게 해주는 Hook이다.
React는 렌더링 과정에서 DOM을 그리는 데 집중하고, 외부 작업(API 호출, 로깅 등)은 useEffect에 맡긴다.
useEffect(() => {
// 이곳에 실행할 부수 효과 로직을 작성한다
}, [dependencies]);
setTimeout, setInterval)import { useEffect, useState } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(data => setUsers(data));
}, []); // 빈 배열: 컴포넌트가 처음 마운트될 때 한 번만 실행
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
[]이기 때문에 마운트 시점에만 한 번 실행된다.setUsers로 상태를 업데이트하면, 컴포넌트는 재렌더링된다.| 의존성 배열 | 동작 설명 |
|---|---|
| 없음 | 렌더링될 때마다 실행됨 |
[] | 처음 마운트될 때 한 번만 실행됨 |
[count] | count 값이 바뀔 때마다 실행됨 |
// 의존성 배열이 없는 경우: 매 렌더링마다 실행됨
useEffect(() => {
console.log("렌더링마다 실행됨");
});
// 특정 상태에 의존하는 경우
useEffect(() => {
console.log("count가 변경될 때만 실행됨");
}, [count]);
useEffect는 컴포넌트가 언마운트되거나, 다음 Effect가 실행되기 전 정리 작업을 할 수 있도록 함수를 반환할 수 있다.
useEffect(() => {
const id = setInterval(() => {
console.log("타이머 실행 중...");
}, 1000);
return () => {
clearInterval(id); // 컴포넌트가 사라질 때 타이머 해제
console.log("타이머 정리됨");
};
}, []);
정리(clean-up)는 메모리 누수나 이벤트 중복을 방지하는 데 중요하다.
의존성 배열을 잘못 작성하면 다음과 같은 문제가 생길 수 있다:
React 공식 문서는 ESLint 규칙(react-hooks/exhaustive-deps)을 사용하여 자동으로 경고를 주도록 권장한다.
useEffect(() => {
doSomething(value); // value는 의존성 배열에 포함되어야 함
}, []); // ❌ value 누락
useEffect는 렌더링 이후 부수 효과(사이드 이펙트) 를 실행하는 Hook이다.return 문을 통해 정리(clean-up) 작업을 정의할 수 있다.useEffect 사용은 오히려 코드를 복잡하게 만들 수 있으므로, 정확한 시점에만 효과를 적용하는 게 핵심이다.React에서 useEffect는 상태 변화에 따른 외부 작업을 다루기 위한 사이드 이펙트 전용 영역이다.
함수형 컴포넌트에서는 필수적인 개념이며, 상태와 동기화되는 동작(API 요청, 타이머, 구독 등)을 처리할 때 유용하게 사용된다.