React의 useEffect 훅과 사이드 이펙트 알아보기
React의 useEffect와 사이드 이펙트 이해하기
React 애플리케이션을 개발하다 보면 컴포넌트 내에서 데이터 페칭, DOM 조작, 구독(subscription) 설정 등 다양한 사이드 이펙트를 다뤄야 할 때가 있습니다. 오늘은 React의 useEffect 훅을 통해 이러한 사이드 이펙트를 어떻게 효과적으로 관리할 수 있는지 알아보겠습니다.
사이드 이펙트란?
사이드 이펙트는 컴포넌트의 렌더링 과정에서 발생하는 순수하지 않은 동작들을 의미합니다. 예를 들면:
- API 호출
- DOM 직접 조작
- 브라우저 API (localStorage, setTimeout 등) 사용
- 구독 설정 및 해제
이러한 작업들은 컴포넌트의 렌더링에 직접적인 영향을 미치지 않지만 애플리케이션의 동작에 필수적인 요소들입니다.
useEffect 개요와 기본 사용법
useEffect는 리액트에서 이러한 사이드 이펙트를 관리할 수 있는 훅입니다.
useEffect는 다음과 같은 형태로 사용됩니다:
useEffect(() => {
// 실행할 사이드 이펙트 코드
return () => {
// 클린업 함수 (선택적)
};
}, [/* 의존성 배열 */]);
의존성 배열에 관찰할 상태를 넣고, 해당 상태가 변하면 사이드 이펙트 코드를 발생시킵니다.
간단한 예제를 통해 살펴보겠습니다:
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchUser = async () => {
try {
setLoading(true);
const response = await fetch(`https://api.example.com/users/${userId}`);
const data = await response.json();
setUser(data);
} catch (error) {
console.error('사용자 정보를 불러오는데 실패했습니다:', error);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]); // userId가 변경될 때마다 실행
if (loading) return <div>로딩 중...</div>;
if (!user) return <div>사용자를 찾을 수 없습니다.</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
useEffect의 의존성 배열
useEffect의 두 번째 인자로 전달되는 의존성 배열은 매우 중요합니다. 세 가지 사용 방식이 있습니다:
빈 배열 (
[]
)useEffect(() => { console.log('컴포넌트가 마운트될 때만 실행됩니다'); }, []);
의존성이 있는 배열 (
[dep1, dep2]
)function Counter({ initialCount }) { const [count, setCount] = useState(initialCount); useEffect(() => { document.title = `현재 카운트: ${count}`; }, [count]); // count가 변경될 때마다 실행 return ( <button onClick={() => setCount(count + 1)}> 증가 </button> ); }
의존성 배열 생략
useEffect(() => { console.log('모든 렌더링마다 실행됩니다'); });
클린업 함수 사용하기
타이머나, 구독형 디자인 패턴을 활용한 사이드 이펙트의 경우 코드가 종료될 때 클린업해주는 것이 일반적입니다.
useEffect에서는 return 값에 클린업 함수를 추가하여 쉽게 적용할 수 있습니다.
클린업 함수는 컴포넌트가 언마운트되거나 의존성이 변경되기 전에 실행됩니다. 구독 해제나 타이머 정리 등에 유용합니다:
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
const handleMessage = (message) => {
setMessages(prev => [...prev, message]);
};
connection.on('message', handleMessage);
// 클린업 함수
return () => {
connection.off('message', handleMessage);
connection.disconnect();
};
}, [roomId]);
return (
<div>
{messages.map(msg => (
<div key={msg.id}>{msg.text}</div>
))}
</div>
);
}
useEffect 사용 시 주의사항
useEffect로 사이드 이펙트를 구현할 때는 다음과 같은 주의사항을 준수해야 합니다.
- 무한 루프 방지하기
// ❌ 잘못된 사용 useEffect(() => { setCount(count + 1); }, [count]); // count가 변경될 때마다 effect가 실행되어 무한 루프 발생
// ✅ 올바른 사용
useEffect(() => {
setCount(prev => prev + 1);
}, []); // 마운트 시에만 실행
2. **불필요한 의존성 피하기**
```javascript
// ❌ 불필요한 의존성
const handleScroll = () => {
console.log(count);
};
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [handleScroll]); // 매 렌더링마다 새로운 함수가 생성됨
// ✅ useCallback 사용하기
const handleScroll = useCallback(() => {
console.log(count);
}, [count]);
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [handleScroll]);
정리
useEffect는 React 컴포넌트에서 사이드 이펙트를 관리하는 강력한 도구입니다. 의존성 배열을 올바르게 관리하고, 클린업 함수를 적절히 사용한다면 메모리 누수를 방지하고 효과적인 애플리케이션 코드를 작성할 수 있습니다.