React Hooks 도입 후 커스텀 훅으로 API 호출 로직 분리하기
배경
팀에서 React Hooks 도입을 결정한 후 기존 클래스 컴포넌트를 함수형으로 전환하는 작업을 진행 중이다. 여러 컴포넌트에서 API 호출 시 loading, error, data 상태를 각각 관리하는 코드가 반복되는 것을 발견했다.
기존 코드의 문제
각 컴포넌트마다 비슷한 패턴이 반복되었다.
function UserList() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [users, setUsers] = useState([]);
useEffect(() => {
setLoading(true);
fetch('/api/users')
.then(res => res.json())
.then(data => setUsers(data))
.catch(err => setError(err))
.finally(() => setLoading(false));
}, []);
// ...
}
커스텀 훅으로 추상화
useFetch라는 커스텀 훅을 만들어 로직을 분리했다.
function useFetch(url) {
const [state, setState] = useState({
loading: true,
error: null,
data: null
});
useEffect(() => {
let cancelled = false;
fetch(url)
.then(res => res.json())
.then(data => {
if (!cancelled) {
setState({ loading: false, error: null, data });
}
})
.catch(error => {
if (!cancelled) {
setState({ loading: false, error, data: null });
}
});
return () => {
cancelled = true;
};
}, [url]);
return state;
}
사용 예시
컴포넌트 코드가 훨씬 간결해졌다.
function UserList() {
const { loading, error, data: users } = useFetch('/api/users');
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
return <ul>{users.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
}
개선 효과
- 보일러플레이트 코드 감소
- 상태 관리 로직 일관성 확보
- cleanup 로직(cancelled 플래그)을 한 곳에서 관리
- 테스트 용이성 향상
다음에는 POST, PUT 등 다른 HTTP 메서드도 지원하도록 확장할 예정이다.