useFetch는 서버에서 fetch한 데이터를 상태값으로 추적하고, 로딩과 에러 상태까지 함께 관리하는 훅입니다. 같은 fetch 로직을 컴포넌트마다 반복해서 작성하는 대신 하나의 훅으로 묶어 재사용할 수 있습니다.
먼저 useState만으로 만든 버전입니다. 응답 데이터, 로딩 여부, 에러를 각각 별도의 상태값으로 관리합니다. 컴포넌트가 언마운트됐을 때 응답 처리를 건너뛰기 위해 shouldCancel 플래그를 사용합니다.
javascriptfunction useFetch(url) { // 응답 JSON값 const [responseJSON,setResponseJSON] = useState(null); // 로딩 상태값 const [isLoading,setIsLoading] = useState(true); // 에러 상태값 const [error,setError] = useState(null); useEffect(()=>{ // unMount시 cancel하기 위한 변수 let shouldCancel = false; const callFetch = async () => { setIsLoading(true); try { // 응답 성공 시 const response = await fetch(url); const newResponseJSON = await response.json(); if(shouldCancel) return; setResponseJSON(newResponseJSON); setError(null); } catch (newError) { // 응답 실패 시 if(shouldCancel) return; setError(newError); setResponseJSON(null); } // 로딩은 응답 실패하나 성공하나 false로 변경 setIsLoading(false) } callFetch(); return () => { shouldCancel = true } },[url]) return { responseJSON, isLoading, error } }
동작에는 문제가 없지만, 관리해야 할 상태값이 세 개로 나뉘어 있어 코드가 다소 복잡하게 느껴집니다. 응답 성공과 실패에 따라 여러 상태를 각각 맞춰 줘야 하는 점도 부담입니다.
흩어진 상태값을 하나로 모으기 위해 useReducer를 사용합니다. 로딩, 성공, 에러라는 상태 전이를 reducer 안에서 한 번에 정의하면, 컴포넌트에서는 dispatch로 의도만 표현하면 됩니다.
javascriptfunction reducer(state,{type,responseJSON,error}) { switch(type) { case 'loading': return {...state,isLoading:true}; case 'success': return {responseJSON,isLoading:false,error:null}; case 'error': return {responseJSON:null,isLoading:false,error}; default: throw new Error('Unknown action type'); } } function useFetch(url) { const [state,dispatch] = useReducer(reducer,{ responseJSON:null, isLoading:true, error:null }) useEffect(()=>{ let shouldCancel = false; const callFetch = async () => { dispatch({type:'loading'}) try { const response = await fetch(url); const newResponseJSON = await response.json(); if(shouldCancel) return; dispatch({type:'success',responseJSON:newResponseJSON}); } catch (newError) { if(shouldCancel) return; dispatch({type:'error',error:newError}); } } callFetch(); return () => { shouldCancel = true } },[url]) return state }
상태값을 하나하나 set하는 대신 loading, success, error 액션만 dispatch하면 되므로, 각 상황에서 어떤 상태가 되는지 reducer만 보면 한눈에 파악됩니다.
기본 버전과 reducer 버전 두 가지로 useFetch 훅을 만들어 봤습니다. 결과는 동일하지만, 기본 버전은 관리할 상태값이 많아 조금 복잡한 느낌이 듭니다. useReducer를 사용하면 상태 전이를 한곳에 모아 더 간결하고 직관적인 코드를 만들 수 있습니다.
// Comments