React useEffect와 빈 의존성 배열: 마운트/언마운트 생명주기 이해하기
React에서 useEffect는 가장 많이 사용되는 Hook 중 하나다. 특히 의존성 배열을 빈 배열(`[]`)로 설정하는 경우가 자주 있는데, 이것이 정확히 어떤 의미를 가지는지, 언제 사용해야 하는지 자세히 알아보자.
useEffect와 빈 의존성 배열의 의미
`useEffect`에 빈 의존성 배열을 전달하면, 해당 효과는 컴포넌트가 처음 마운트될 때 단 한 번만 실행된다. 또한 cleanup 함수가 있다면, 이는 컴포넌트가 언마운트될 때 한 번 실행된다.
import { useEffect } from 'react';
function ExampleComponent() {
useEffect(() => {
console.log('컴포넌트가 마운트되었다!');
// cleanup 함수
return () => {
console.log('컴포넌트가 언마운트되었다!');
};
}, []); // 빈 의존성 배열
return <div>예제 컴포넌트</div>;
}
빈 의존성 배열을 사용하는 대표적인 경우들
1. 초기 데이터 로딩
API에서 초기 데이터를 가져올 때 가장 흔히 사용된다.
function UserProfile() {
const [userData, setUserData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUserData = async () => {
try {
const response = await fetch('https://api.example.com/user');
const data = await response.json();
setUserData(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchUserData();
}, []); // 컴포넌트 마운트 시 한 번만 실행
if (loading) return <div>로딩 중...</div>;
if (error) return <div>에러 발생: {error}</div>;
return (
<div>
<h1>{userData?.name}의 프로필</h1>
<p>이메일: {userData?.email}</p>
</div>
);
}
2. 이벤트 리스너 등록 및 제거
window나 document 레벨의 이벤트 리스너를 관리할 때 유용하다.
function ScrollTracker() {
const [scrollY, setScrollY] = useState(0);
useEffect(() => {
const handleScroll = () => {
setScrollY(window.scrollY);
};
// 이벤트 리스너 등록
window.addEventListener('scroll', handleScroll);
// cleanup: 이벤트 리스너 제거
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
return (
<div>
<p>현재 스크롤 위치: {scrollY}px</p>
</div>
);
}
3. WebSocket 연결 관리
실시간 통신을 위한 WebSocket 연결을 설정하고 정리할 때 사용된다.
function ChatRoom() {
const [messages, setMessages] = useState([]);
useEffect(() => {
const socket = new WebSocket('wss://chat.example.com');
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
setMessages(prev => [...prev, message]);
};
socket.onopen = () => {
console.log('WebSocket 연결 성공!');
};
// cleanup: WebSocket 연결 종료
return () => {
socket.close();
};
}, []);
return (
<div>
{messages.map((msg, index) => (
<div key={index}>{msg.text}</div>
))}
</div>
);
}
4. 타이머 설정 및 정리
주기적으로 실행되어야 하는 작업을 관리할 때 사용한다.
function AutoRefresh() {
const [lastUpdate, setLastUpdate] = useState(new Date());
useEffect(() => {
const interval = setInterval(() => {
setLastUpdate(new Date());
}, 1000);
// cleanup: 타이머 정리
return () => {
clearInterval(interval);
};
}, []);
return (
<div>
<p>마지막 업데이트: {lastUpdate.toLocaleTimeString()}</p>
</div>
);
}
주의사항과 고려할 점
1. 불필요한 빈 배열 사용 피하기
// ❌ 잘못된 사용
function UserGreeting({ name }) {
useEffect(() => {
console.log(`Hello, ${name}!`);
}, []); // name이 변경되어도 실행되지 않음
return <h1>Welcome, {name}!</h1>;
}
// ✅ 올바른 사용
function UserGreeting({ name }) {
useEffect(() => {
console.log(`Hello, ${name}!`);
}, [name]); // name이 변경될 때마다 실행
return <h1>Welcome, {name}!</h1>;
}
2. 비동기 작업 처리 시 주의사항
function AsyncComponent() {
const [data, setData] = useState(null);
useEffect(() => {
let isSubscribed = true;
const fetchData = async () => {
const result = await fetch('https://api.example.com/data');
const json = await result.json();
// 컴포넌트가 언마운트되지 않았을 때만 상태 업데이트
if (isSubscribed) {
setData(json);
}
};
fetchData();
return () => {
isSubscribed = false;
};
}, []);
return <div>{data ? JSON.stringify(data) : '로딩 중...'}</div>;
}
빈 의존성 배열을 사용하는 useEffect는 다음과 같은 특징을 가진다
1. 컴포넌트 마운트 시 단 한 번만 실행된다
2. cleanup 함수는 컴포넌트 언마운트 시 실행된다
3. 주로 초기 설정, 구독, 이벤트 리스너 등을 관리할 때 사용한다
4. 불필요하게 사용하면 버그의 원인이 될 수 있다
이러한 특징을 잘 이해하고 적절한 상황에서 사용한다면, 컴포넌트의 생명주기를 효과적으로 관리할 수 있다. 하지만 무분별한 사용은 피하고, 실제로 의존성이 필요한 경우에는 해당 값들을 의존성 배열에 포함시키는 것이 좋다.
React 개발을 하면서 useEffect와 의존성 배열을 올바르게 사용하는 것은 매우 중요하다. 특히 빈 의존성 배열의 의미를 정확히 이해하고 적절한 상황에서 활용한다면, 더 안정적이고 예측 가능한 컴포넌트를 만들 수 있을 것이다.