React Hooks 도입 후 Custom Hook으로 API 호출 로직 분리하기
배경
올해 초 React 16.8이 정식 릴리즈되면서 Hooks를 프로덕션에 사용할 수 있게 되었다. 기존 Class 컴포넌트로 작성된 프로젝트를 점진적으로 리팩토링하고 있는데, API 호출 로직이 컴포넌트마다 중복되는 문제가 여전했다.
기존 방식의 문제
각 컴포넌트에서 componentDidMount나 useEffect마다 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(setUsers)
.catch(setError)
.finally(() => setLoading(false));
}, []);
// ...
}
useFetch 커스텀 훅
공통 로직을 분리한 Custom Hook을 만들었다.
function useFetch(url) {
const [state, setState] = useState({
loading: true,
data: null,
error: null
});
useEffect(() => {
let cancelled = false;
fetch(url)
.then(res => res.json())
.then(data => {
if (!cancelled) {
setState({ loading: false, data, error: null });
}
})
.catch(error => {
if (!cancelled) {
setState({ loading: false, data: null, error });
}
});
return () => {
cancelled = true;
};
}, [url]);
return state;
}
컴포넌트 코드가 훨씬 간결해졌다.
function UserList() {
const { loading, data: users, error } = 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>;
}
배운 점
- Custom Hook으로 상태 로직 재사용이 HOC나 Render Props보다 직관적이다
- cleanup 함수로 언마운트 시 메모리 누수를 방지해야 한다
- Hook의 의존성 배열 관리가 생각보다 중요하다
다음엔 SWR이나 React Query 같은 라이브러리도 검토해볼 예정이다.