React Hooks 도입 후 Custom Hook으로 API 호출 로직 정리하기
배경
팀 프로젝트에서 React 16.8로 업그레이드하면서 본격적으로 Hooks를 도입하기 시작했다. 기존에는 API 호출을 위해 HOC와 Render Props 패턴을 혼용했는데, 컴포넌트 depth가 깊어지고 props drilling 문제가 심각했다.
기존 코드의 문제
<DataFetcher url="/api/users">
{({ data, loading, error }) => (
<WithAuth>
{({ user }) => (
<UserList data={data} loading={loading} />
)}
</WithAuth>
)}
</DataFetcher>
중첩이 심하고, 로직 재사용이 어려웠다.
useFetch Custom Hook
function useFetch(url) {
const [state, setState] = useState({
data: null,
loading: true,
error: null
});
useEffect(() => {
let cancelled = false;
fetch(url)
.then(res => res.json())
.then(data => {
if (!cancelled) {
setState({ data, loading: false, error: null });
}
})
.catch(error => {
if (!cancelled) {
setState({ data: null, loading: false, error });
}
});
return () => {
cancelled = true;
};
}, [url]);
return state;
}
개선된 컴포넌트
function UserList() {
const { data, loading, error } = useFetch('/api/users');
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
return <List items={data} />;
}
훨씬 간결해졌다. cleanup 로직도 useEffect의 return으로 자연스럽게 처리된다.
추가 개선 포인트
- 재시도 로직을 추가한 useRetryFetch
- 캐싱이 필요한 경우 useRef로 메모이제이션
- AbortController를 사용한 요청 취소
Hooks는 확실히 로직 재사용 측면에서 HOC보다 직관적이다. 다만 useEffect의 dependency array 관리에 주의가 필요하다. ESLint의 exhaustive-deps 규칙을 켜두는 게 도움이 됐다.