axios는 JavaScript에서 HTTP 요청을 보내는 데 사용되는 라이브러리이다. 주로 웹 애플리케이션에서 서버와의 통신을 위해 사용되며, 클라이언트 측에서 서버로 데이터를 보내거나 서버로부터 데이터를 받아올 때 유용하게 활용된다.
처음에 axios와 fetch의 차이점을 잘 알지 못하고 나를 포함한 모든 팀원들에게 익숙한 fetch를 사용하기로 했다. fetch를 사용할때 그래도 코드가 반복되는게 싫어서 CommonAPI.js 파일을 만들어 미리 사용할 api 메서드들을 정의해서 사용했다.
// CommonAPI.js 일부 (fetch 사용)
/* ---------------------------- GENERAL API */
const API_NO_BODY = async (method, token, reqUrl) => {
const res = await fetch(url + reqUrl, {
method: method,
headers: {
'Authorization': `Bearer ${token}`,
'Content-type': 'application/json',
},
});
const json = await res.json();
return json;
};
const API_BODY = async (method, token, reqUrl, bodyData) => {
const res = await fetch(url + reqUrl, {
method: method,
headers: {
'Authorization': `Bearer ${token}`,
'Content-type': 'application/json',
},
body: JSON.stringify(bodyData),
});
const json = await res.json();
return json;
};
/* ---------------------------- GET API */
const GET_API = async (token, reqUrl) => {
return await API_NO_BODY('GET', token, reqUrl);
};
/* ---------------------------- POST API */
const POST_API = async (token, reqUrl, bodyData) => {
return await API_BODY('POST', token, reqUrl, bodyData);
};
✔️ 코드의 반복 최소화: 위의 메서드들을 살펴보면 매번 token을 넣어야 했고, 메서드를 불러오는 파일에서 매번 recoilState를 사용해서 token을 불러와야했다. 무언가 한번에 설정해줄 수 있는게 있으면 api 파일 뿐만 아니라 다른 파일에서도 많은 코드 수를 줄일 수 있을 것 같았다 => axios의 interceptors을 사용하면 token을 받아오거나 없애는 순간 authorization을 설정해 줄 수 있어서 코드가 많이 간략해질 것을 기대했다.
✔️ 일괄 에러 처리: api 관련해서 에러가 났을 때 일일이 처리해주는 것이 번거로워서 잠깐 미루고 있었다.=> axios의 interceptors을 사용하면 조금 더 쉽고 간단하게 에러 핸들링을 할 수 있을 것을 기대했다.
import axios from 'axios';
const instance = axios.create({
baseURL: 'https://api.example.com',
});
instance.defaults.headers.post['Content-Type'] = 'application/json';
instance.defaults.headers.put['Content-Type'] = 'application/json';
axios의 create 메서드를 사용하여 인스턴스를 생성한다. baseURL은 모든 요청에 앞으로 추가될 기본 URL을 설정한다. 이렇게 하면 각 요청에서 baseURL을 반복해서 입력하지 않아도 된다.
instance.defaults.headers.post['Content-Type'] = 'application/json';
: 생성한 axios 인스턴스의 defaults.headers를 사용하여 기본 헤더를 설정한다.
이렇게 생성한 instance를 사용하면 baseURL과 헤더 설정이 기본적으로 포함된 상태로 요청을 보낼 수 있다.
이미 대부분의 코드를 위의 CommonAPI를 사용했기에 CommonAPI에 있는 메서드들은 최대한 유지하면서 axios를 사용하기로 했다.
import api from './index';
/* ---------------------------- GET API */
const GET_API = async (reqUrl) => {
try {
const response = await api.get(reqUrl);
return response.data;
} catch (error) {
console.error(error);
}
};
const POST_API = async (reqUrl, bodyData = {}) => {
try {
const response = await api.post(reqUrl, bodyData);
return response.data;
} catch (error) {
console.error(error);
}
};
instance.interceptors.request.use(
(config) => {
const token = JSON.parse(localStorage.getItem('recoil-persist')).userToken;
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
} else {
delete config.headers['Authorization'];
}
return config;
},
(error) => {
return Promise.reject(error);
},
);
localStorage.getItem('recoil-persist').userToken
을 통해 로컬 스토리지에서 유저 토큰을 가져온다.instance.defaults.headers['Authorization'] = 'Bearer ' + token;
이런 식으로 설정해주려했지만 그 순간만 설정될 뿐 계속 지속되지 않았다. instance.interceptors.response.use(
(response) => response,
(error) => {
// error 처리
return Promise.reject(error);
},
);
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
import { useRef, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import instance from './index';
export default function AxiosNavigation() {
const navigate = useRef(useNavigate());
useEffect(() => {
const intercetpor = instance.interceptors.response.use(
(response) => response,
(error) => {
if (error.code === 'ERR_NETWORK') {
navigate.current('/error', {
state: {
error_code: error.code,
},
});
}
if (error.response?.status < 200 || error.response?.status > 299) {
navigate.current('/error');
}
return Promise.reject(error);
},
);
return () => {
instance.interceptors.response.eject(intercetpor);
};
}, []);
return <></>;
}
// index.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
import { RecoilRoot } from 'recoil';
import { QueryClient, QueryClientProvider } from 'react-query';
import { HelmetProvider } from 'react-helmet-async';
import AxiosNavigation from './api/AxiosNavigation';
const container = document.getElementById('root');
const queryClient = new QueryClient();
const root = createRoot(container);
root.render(
<BrowserRouter basename={process.env.PUBLIC_URL}>
<AxiosNavigation />
<HelmetProvider>
<RecoilRoot>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</RecoilRoot>
</HelmetProvider>
</BrowserRouter>,
);
<BrowserRouter>
안에 위치시킨다.