ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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 urlmethod를 인자로 보내주면 정해진 요청을 처리하고 응답 데이터를 반환하는 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 요청마다 로그인  여부를 확인해 헤더에 넣지 않아도 됩니다.

     

    [axios] interceptors로 요청/응답 전 작업하기

    요청 전 처리가 가능한 interceptors로 모든 요청 전에 access토큰 만료시간 체크하기!!이미 요청을 보낸 후에 만료됐는지 체크하면 늦었기 때문이다.access토큰 있으면 JWT 디코딩해서 만료시간 체크만

    velog.io

     

     

    Example of Axios with TypeScript

    Example of Axios with TypeScript . GitHub Gist: instantly share code, notes, and snippets.

    gist.github.com

     

     

     

     

    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를 실행하지 않을 것이므로 효율적이라 생각합니다. 이게 적절한 예시가 맞는지는 조금 더 다양한 상황을 닥쳐보고 코드를 작성하면서 곱씹어봐야할 것 같습니다.

     

     

     

     

    참고 사이트

     

    [React.js] 커스텀 훅(Custom Hook)과 유틸 함수(util function)에 대해서

    ❗ 커스텀 훅(Custom Hook) React에서 커스텀 훅(Custom Hook)은 상태 로직(stateful logic)을 재사용할 수 있도록 하는 기능이다. 이는 여러 컴포넌트에서 공통적으로 사용되는 상태 로직을 추출하여 하나의

    growing-jiwoo.tistory.com

     

    Axios Custom Hook TypeScript 환경으로 만들기

    안녕하세요 웹 프론트엔드 개발자 설탕시럽입니다. 프로젝트 내 axios를 활용한 중복 코드 제거를 위해 Axios Custom Hook을 만든 경험을 공유하고자 합니다. axios에서 중복되는 설정과 로직을 React의 H

    sugarsyrup.tistory.com

     

Designed by Tistory.