-
API 요청 코드 분리하기(일반 함수 vs 커스텀 훅)Studying/React 2023. 7. 6. 10:00
개요
서버에 API 요청을 할 때, 매번 비슷한 형태의 코드를 작성하기 귀찮아서 주로 call 이라는 이름의 일반 함수(유틸util 함수)를 별도로 분리한 뒤 호출해 사용했습니다. 그런데 문득 일반 함수로 분리하는 것과 커스텀 훅으로써 분리하는 것 사이에 무슨 차이가 있는지 궁금해져 비교해보려 합니다.
(React + TypeScript + Axios 기반)
일반 함수 call()을 만들어 분리하는 법
자주 사용하는 기능을 모듈화 하여 재사용하는 함수를 유틸 함수(util function)라고 합니다.
(저는 보통 utils 폴더에 자주 사용되는 함수들을 별도의 파일로 분리하여 관리했습니다.)
서버에 데이터를 요청하는 API 요청 코드 또한 특별한 경우가 아닌 이상 대부분 같은 구조를 반복합니다.
axios를 사용하여 GET 요청을 하는 경우,
const axios = require('axios'); axios.get('https://example.com/posts/1') .then(response => console.log(response.data)) .catch(error => console.error(error))
axios를 사용하여 POST 요청을 하는 경우,
const axios = require('axios'); axios.post('https://example.com/posts', { title: 'foo', body: 'bar', userId: 1 }) .then(response => console.log(response.data)) .catch(error => console.error(error))
body를 함께 보내느냐 마느냐의 차이일 뿐 비슷한 형태이며 PUT, DELETE 등 자주 쓰이는 다른 요청들도 마찬가지입니다.
여기에 사용자 인증 토큰까지 더해지면 매번 토큰을 가져오고 요청 헤더에 넣는 긴 코드를 반복 작성해야합니다.
따라서 API url와 method를 인자로 보내주면 정해진 요청을 처리하고 응답 데이터를 반환하는 call() 함수를 별도로 만들어 코드를 단순하게 만들어보겠습니다.
코드는 다음과 같습니다.(에러 처리는 제외했습니다.)
// 필요한 axios 관련 모듈과 서버 url을 import 합니다. import axios, { RawAxiosRequestConfig, AxiosHeaders } from 'axios'; import { API_BASE_URL } from '@/app-config'; const ACCESS_TOKEN = 'accessToken'; export async function call(url: string, method: string, body?: any) { // props: api url 엔드포인트, 요청 method, 요청 body const accessToken = localStorage.getItem(ACCESS_TOKEN); const headers = new AxiosHeaders({ 'Content-Type': 'application/json', 'Athorization' : `${accessToken}`, }); const options: RawAxiosRequestConfig = { headers: headers, method: method, url: API_BASE_URL + url, }; if (body) { options.data = JSON.stringify(body); } const response = await axios(options); return response.data; }
이제 이 유틸 함수를 가져와 사용해보도록 하겠습니다.
GET 요청의 경우,
const getPortfolio = () => call(`/portfolios/${portfolioId}`, 'GET'); useEffect(() => { async function getAndSetPortfolio() { 1 const portfolio = await await getPortfolio(=); setPortfolio(res.data); } getAndSetPortfolio(); }, []);
POST 요청의 경우,
const postPortfolio = (body: PortfolioContent) => call('/portfolios', 'POST', body); await postPortfolio(submissionPortfolio);
이런 식으로 사용할 수 있습니다.
call() 함수의 경우 그냥 곧바로 call()이라고 사용하는 것보다, 그때의 상황에 맞는 함수명을 짓고 한 번 감싸서 사용하는 게 가독성이 훨씬 좋습니다.
아쉬운 점이라면 요청 body를 뭐든 받을 수 있게 any 타입으로 놔둔 점이 조금 불편하긴 합니다.
또한 여기선 사용하지 않았지만, axios interceptors 를 사용하여 요청 전에 특정 작업(헤더에 Authorization을 담는 등)을 수행할 수 있습니다. 이 방법을 사용하면 API 요청마다 로그인 여부를 확인해 헤더에 넣지 않아도 됩니다.
API 요청 커스텀 훅을 만들어 분리하는 법
가장 기본적으로 useFetch라는 커스텀 훅을 만들어 반복 사용합니다.
코드는 다음과 같습니다.
import { API_BASE_URL } from '@/app-config'; import axios, { RawAxiosRequestConfig, AxiosHeaders } from 'axios'; const ACCESS_TOKEN = 'accessToken'; const useCall = ( initialUrl:string, method: string, body?:any ) => { const [url, setUrl] = useState(initialUrl); const [value, setValue] = useState(''); const accessToken = localStorage.getItem(ACCESS_TOKEN); const headers = new AxiosHeaders({ 'Content-Type': 'application/json', accessToken: `${accessToken}`, }); const options: RawAxiosRequestConfig = { headers: headers, method: method, url: API_BASE_URL + api, }; if (body) { options.data = JSON.stringify(body); } const fetchData = async () => { const response = await axios(options); setValue(response); } useEffect(() => { fetchData(); },[url]); return [value]; }; export default useCall;
다음과 같이 사용할 수 있습니다.
import useFetch from "./util/useCall"; export default function App() { const getData = useCall("data.json", 'GET'); return ( ... ); }
왠지 유틸 함수와 비슷하면서도 훅이라는 점 + 상태를 관리한다는 점에서 작은 차이가 있습니다.
유틸 함수와 커스텀 훅의 차이점
결국 일반 함수와 커스텀 훅은 (당연한 말이지만) 훅이냐, 아니냐의 차이 같습니다.
일반 함수의 경우 특정 작업을 독립적으로 실행하는 함수입니다.
실행 코드의 여러 부분에서 재사용되고, 입력 인수를 받아 계산을 수행한 뒤 결과를 반환합니다.
커스텀 훅은 React 내장 hook을 사용하기 때문에 몇가지 규칙을 지켜야 합니다.
또, 어떤 '특정 기능'을 한 번 수행할 때 사용하기보다는, 어떤 상태를 주기적으로 업데이트 시키는 반복적인 행동을 정의할 때 사용하면 좋습니다.
예를들어, 유틸 함수는 'Date를 0000-00-00 같은 특정 포맷으로 반환' 하는 기능을 가진다면(한 번 호출되면 끝)
커스텀 훅은 '초마다 숫자가 늘어나는 타이머' 같은 주기적으로 변하는 기능을 구현할 때 사용(반복되는 작업)하는 것입니다. 타이머의 경우 time 이라는 상태를 만들어 매 초마다 이전 time + 1 값이 업데이트되며 반환되게 만들 수 있습니다.
결론적으로 API 호출 함수의 경우에는 어떤 방법을 사용하는 게 좋을까 생각해보니... 특별한 경우가 아닌 이상 비슷하게 느껴집니다.
하지만 억지로 머리를 굴려 예시를 떠올려보자면... 이미 추천한 글에 계속해서 '추천 버튼'을 클릭하는(추천 취소가 안됨) 상황을 가정하겠습니다. 커스텀 훅이라면 매 요청마다 클라이언트의 추천 여부를 확인할 필요 없이 이전 요청 url과 동일할 경우 useEffect를 실행하지 않을 것이므로 효율적이라 생각합니다. 이게 적절한 예시가 맞는지는 조금 더 다양한 상황을 닥쳐보고 코드를 작성하면서 곱씹어봐야할 것 같습니다.
참고 사이트
'Studying > React' 카테고리의 다른 글
useQuery GET으로 받은 데이터가 props로 전달되지 않을 경우 (0) 2023.07.10 [Custom Hook] return하는 데이터 타입이 전부 유니온으로 합쳐지는 경우 (0) 2023.07.09 [Storybook] Storybook + Twin.macro 환경 설정 (0) 2023.07.01 twin + styled 동적 할당 시 tw 사용이 불가능한 이유 (0) 2023.07.01 Styled-Component에서는 id props를 사용할 수 없나요? (0) 2023.06.27