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
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를 마음껏 사용할 수 있게 되었다.
'React 관련 > 프로젝트' 카테고리의 다른 글
[React] 우리집 고양이도 할 수 있는 카카오 로그인 구현(with firebase OAuth 2.0 소셜 로그인) (2) | 2024.08.12 |
---|---|
[React] 구글 로그인 기능 구현(with firebase 소셜 로그인 인증) (0) | 2024.08.07 |
[React-Redux] 실제 프로젝트에 적용하고, thunk로 비동기 통신 구현 (0) | 2024.05.08 |
React 구글맵(googleMap) 최적화(with typescript) (2) | 2024.04.26 |
React로 간단히 만들어보는 구글맵(with Typescript) (0) | 2024.04.05 |