본문 바로가기
React 관련/프로젝트

React의 Typescript와 함께 data fetching(axios로 API 호출)

by ash9river 2024. 4. 21.

Typescript

typescript를 사용함에 따라 기존의 코드에서 사용하던 axios가 어려워져버렸다...

 

기존의 자바스크립트를 이용한 axios는 간단하고 데이터를 쉽게 받아올 수 있었으나, Dynamically Typed Language라는 말에 걸맞게, 불러오는 데이터의 형식을 전혀 알 수 없었다.

 

다음과 같은 js 코드를 보자, 내 취향껏 짠 코드이다. 엄청 간단하고 명확하지만, 어떤 데이터를 주고 받는지 알 수 없다.

import axios from 'axios';

export async function getItems() {
  return new Promise((resolve, reject) => {
    (async () => {
      try {
        const res = await axios.get('http://localhost:3000/meals');
        resolve(res.data);
      } catch (err) {
        reject(err);
      }
    })();
  });
}
export async function postItems(data) {
  return new Promise((resolve, reject) => {
    (async () => {
      try {
        const res = await axios.post('http://localhost:3000/orders', data, {
          headers: {
            'Content-Type': 'application/json',
          },
        }); 

        resolve(res.data);
      } catch (err) {
        reject(err);
      }
    })();
  });
}

 

그래서 타입스크립트를 통해 데이터의 타입을 정의해줄 필요성이 생겼고, axios를 타입스크립트와 함께 이용할 때, 기존과는 다른 새로운 기능들을 이용하겠다.

 

만약 axios의 기초도 모른다면 inpa의 블로그에서 공부해보자

 

https://inpa.tistory.com/entry/AXIOS-%F0%9F%93%9A-%EC%84%A4%EC%B9%98-%EC%82%AC%EC%9A%A9

 

📚 AXIOS 설치 & 특징 & 문법 💯 정리

Axios 라이브러리 Axios는 브라우저, Node.js를 위한 Promise API를 활용하는 HTTP 비동기 통신 라이브러리 아다. 쉽게 말해서 백엔드랑 프론트엔드랑 통신을 쉽게하기 위해 Ajax와 더불어 사용한다. 이미

inpa.tistory.com

API 모듈화

실제로 코드를 사용할 때, 한 파일 안의 코드가 너무 긴 경우에는 가독성에도 좋지 않고, 생산성을 저하시킬 우려가 있다.(협업시 충돌이 일어날 경우가 많아진다.)

 

그래서 코드를 기능적인 분리를 시켜서 유지보수와 코드 재사용성을 높일 수 있도록 설계해야 한다. 이를 모듈화라고 하는데, axios에서도 모듈화를 AxiosInstance를 통해 이뤄낼 수 있다.

 

AxiosInstance

먼저, axios.create로 API 모듈화의 첫발을 딛어보자.

import axios, { AxiosInstance } from 'axios';

const apiRequester: AxiosInstance = axios.create({
  baseURL: 'http://localhost:3000',
  timeout: 5000,
});

 

apiRequester라는 이름으로 AxiosInstance를 만들었다.

 

만약, baseURL이 개발 과정이 아니라면 .env에서 다음과 같이 URL를 불러오면 된다.

import axios, { AxiosInstance } from 'axios';

const apiRequester: AxiosInstance = axios.create({
  baseURL: process.env.REACT_APP_URL as string,
  timeout: 5000,
});

 

이제 기본 url은 세팅해두었으니, config를 설정해보자.

requestConfig

AxiosRequestConfig를 이용해서 다음과 같이 디폴트 설정을 만들었다.

 

입맛대로 수정하면 된다.

import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';

const setRequestDefaultHeader = (requestConfig: AxiosRequestConfig) => {
  const config = requestConfig;
  config.headers = {
    ...config.headers,
    'Content-Type': 'application/json;charset=utf-8',
  };
  return config;
};

 

Axios intercept

 

interceptor를 통해, axios의 기본 응답을 가로채서 설정을 바꿀 수 있는데, 간단한 예시 코드를 작성하였다.

 

request의 data의 인스턴스가 FormData면 Content-Type를 수정하는 방법을 사용할 수 있다.

axios.interceptors.request.use(
  (req) => {
    if (req.data instanceof FormData) {
      req.headers = {
        ...req.headers,
        'Content-Type': 'multipart/form-data',
      };
    }
    return req;
  },
  (err) => {
    return Promise.reject(err);
  },
);

axios get

이제 axios.get을 이용해 데이터를 받아와보자. 이 때, msw를 이용해서 모킹된 데이터를 받아올 것이다.

mocking data

임시로 개발하면서 설정한 데이터를 http 요청을 통해 가져올 때, 백엔드가 구현되어 있지 않으므로, msw를 통해 모킹된 데이터를 가져온다.

// handler.ts
import { http, HttpResponse } from 'msw';

const posts = ['게시글1', '게시글2', '게시글3'];

export interface postItemType {
  postId: number;
  postTitle: string;
  postContent: string;
}

const postArray: postItemType[] = [
  {
    postId: 1,
    postTitle: '가나다라마바사',
    postContent: '아자차카타파하',
  },
  {
    postId: 2,
    postTitle: '가나다라마바사',
    postContent: '아자차카타파하',
  },
  {
    postId: 3,
    postTitle: '가나다라마바사',
    postContent: '아자차카타파하',
  },
];

export const handlers = [
  http.get('/hello', () => {
    console.log('msw:get :: /hello');
    return HttpResponse.json({
      data: 'Captured a "GET /hello" request',
    });
  }),

  http.get('/post', async () => {
    const retItem = postArray;
    console.log(retItem);

    return HttpResponse.json(retItem);
  }),

  http.get('/post/:id', async ({ params }) => {
    const { id } = params;
    const retItem: postItemType | undefined = postArray.find(
      (postItem) => postItem.postId === +id,
    );

    console.log(retItem);

    return HttpResponse.json(retItem);
  }),
];

 

여기서 axios.get('/post')를 통해 postItemType[]의 타입을 가진 postArray를 가져올 것이다.

getData 함수

제네릭 타입을 이용해서 get 요청을 날린다. 여기서 setRequestDeaultHeader는 상단에서 작성한 Config이다.

import axios, { AxiosInstance, AxiosRequestConfig, isAxiosError } from 'axios';

const breaket = {};

export const getData = async <T>(
  url: string,
  config?: AxiosRequestConfig,
): Promise<T> => {
  try {
    const modifiedConfig = setRequestDefaultHeader(config || breaket);
    const response = await apiRequester.get<T>(url, modifiedConfig);
    return response.data;
  } catch (error) {
    if (isAxiosError(error)) throw new Error(error.message);
    else throw error;
  }
};

 

이 getData 함수를 이용하여 이제 데이터를 가져와보자. 이건 상당히 간단하다.

결과

개발 단계이므로 데이터가 가져와지는지 확인만 하면 된다.

import { useEffect, useState } from 'react';
import { getData } from 'services/api-requester';
import { postItemType } from 'services/mocks/handlers';

export default function ThisIsTmp() {
  const [data, setData] = useState<postItemType[]>();

  useEffect(() => {
    async function fetchData() {
      const tmp = await getData<postItemType[]>('/post');
      setData(tmp);
    }

    fetchData();
  }, []);

  return (
    <>
      <p>below</p>
      {data !== undefined ? (
        <ul>
          {data.map((item) => (
            <li key={item.postId}>
              <p>id : {item.postId}</p>
              <p>title : {item.postTitle} </p>
              <p>content : {item.postContent}</p>
            </li>
          ))}
        </ul>
      ) : (
        'awaiting...'
      )}
    </>
  );
}

결과는 잘 나온다

 
주의

 

getData를 호출할 때, await를 붙이지 않으면 타입 추론이 Promise가 resolve되지 않은 형태로 되어버린다.

const tmp = getData<postItemType[]>('/post');

 

api-requester.ts 전체 파일

crud는 다른 파일로 분리하는 편이 낫다고 생각하지만, 일단 임시 개발 단계이므로 하나로 합쳐두었다.

//api-requester.ts
import axios, { AxiosInstance, AxiosRequestConfig, isAxiosError } from 'axios';

interface Response<T> {
  data: T;
  status: string;
  severDataTime?: string;
  errorCode?: string;
  errorMessage?: string;
}

const apiRequester: AxiosInstance = axios.create({
  baseURL: 'http://localhost:3000',
  timeout: 5000,
});

const setRequestDefaultHeader = (requestConfig: AxiosRequestConfig) => {
  const config = requestConfig;
  config.headers = {
    ...config.headers,
    'Content-Type': 'application/json;charset=utf-8',
  };
  return config;
};

const breaket = {};

export const getData = async <T>(
  url: string,
  config?: AxiosRequestConfig,
): Promise<T> => {
  try {
    const modifiedConfig = setRequestDefaultHeader(config || breaket);
    const response = await apiRequester.get<T>(url, modifiedConfig);
    return response.data;
  } catch (error) {
    if (isAxiosError(error)) throw new Error(error.message);
    else throw error;
  }
};

 

이제 typescript를 통해 axios를 마음껏 사용할 수 있게 되었다.