React (Next js) 환경에서 무료 제공 api JSONPlaceholder를 사용하여 Axios Interceptor Customizing 과정을 기록합니다.
각 과정에서의 타입을 비교하며 이해해보세요.
Axios의 interceptor 기능을 활용하여 request, response 요청에 대해 우리의 Project 형식에 맞게 선행, 후행 처리를 제어할 수 있다.
const instance = axios.create({
baseURL: 'https://jsonplaceholder.typicode.com',
});
instance.interceptors.request.use((req) => {
// api 요청이 이루어지기 전 로직설계.
// ex) header 설정
// req.headers.authorization = ~
return req;
});
instance.interceptors.response.use(
(res) => {
// 요청 성공
// 우리의 project에 사용되는 api 응답 성공 데이터 로직설계
console.log(res);
return res.data;
},
(e) => {
// 요청 실패
// 우리의 project에 사용되는 api 응답 실패 데이터 로직설계
console.log(e);
return Promise.reject(e);
},
);
const RESOURCE = 'users';
/*
원래대로라면, response.data에 users 데이터가 들어있어야 한다.
하지만 interceptor를 통해서 요청 성공에 대한
우리가 필요한 Promise 형태의 데이터를 반환하도록 포맷팅하였다.
*/
export const getUsers = () => instance.get(`${RESOURCE}`);
하지만 타입스크립트 적용시 interceptor를 통해 아래의 코드와 같이 api 요청 성공에 대한 응답값을 포맷팅하였지만 타입추론이 올바른 형태로 이루어지지 않음을 볼 수 있다.
getUsers는 AxiosResponse 타입으로 감싸져서, 실제로는 정상적인 데이터 객체를 받아온다.
하지만 타입상으로는 { data: { ~ } }
로 우리가 기대한 Promise형태가 아닌 AxiosResponse형태로 나온다.
그렇기에 Promise형태의 타입이 필요한 경우 아래와 같이 타입 제한에 걸릴 수 있다.
instance.interceptors.response.use(
(res) => {
// 요청 성공
console.log(res);
return res.data;
},
(e) => {
// 요청 실패
console.log(e);
return Promise.reject(e);
},
);
const RESOURCE = 'users';
export interface User {
// ~
}
export const getUsers = () => instance.get<User[]>(`${RESOURCE}`);
// api 요청에 대한 상태값을 훅에서 다루기
// useUserList.ts
const useUserList = () => {
const [userList, setUserList] = useState<User[]>([]);
useEffect(() => {
(async () => {
const userList = await getUsers();
setUserList(userList); // 타입호환이 안되어 에러발생!
console.log(userList, 'userList');
})();
}, []);
return userList;
};
이에 대해 아래와 같이 커스텀한 타입을 추가하여 우리가 원하는 Promise 형태의 데이터를 얻을 수 있다.
hook으로 돌아가 마우스를 올려보며 반환된 타입을 꼭 비교해보자.
export const getUsers = () => instance.get<any, User[]>(`${RESOURCE}`);
// useUserList.ts
const useUserList = () => {
const [userList, setUserList] = useState<User[]>([]);
useEffect(() => {
(async () => {
const userList = await getUsers();
setUserList(userList); // 타입호환 에러 해결
console.log(userList, 'userList');
})();
}, []);
return userList;
};
하지만 위의 방식은 모든 api에 any 타입을 계속해서 지정해야 하므로 불편함을 느끼게 되었다.
그렇기에 타입스크립트에서 타입선언에 대한 여러가지 방법 중 인터페이스의 상속을 통한 customizing을 진행하게 되었다.
import {
AxiosInstance,
AxiosInterceptorManager,
AxiosRequestConfig,
AxiosResponse,
} from 'axios';
//project의 api 응답 객체의 response.data의 타입 형태에 맞게 수정
type ResponseData = any;
export interface Instance extends AxiosInstance {
interceptors: {
request: AxiosInterceptorManager<AxiosRequestConfig>;
response: AxiosInterceptorManager<AxiosResponse<ResponseData>>; // 지정한 타입 지정
};
getUri(config?: AxiosRequestConfig): string;
request<T>(config: AxiosRequestConfig): Promise<T>;
get<T>(url: string, config?: AxiosRequestConfig): Promise<T>;
delete<T>(url: string, config?: AxiosRequestConfig): Promise<T>;
head<T>(url: string, config?: AxiosRequestConfig): Promise<T>;
options<T>(url: string, config?: AxiosRequestConfig): Promise<T>;
post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
patch<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
}
이렇게 Axios Interceptor에 관하여 커스터마이징을 진행해보았다.
커스터마이징을 진행한 후 응답 객체의 데이터의 타입추론이 이루어지는 것 을 볼 수 있다.
글을 작성할 때 다른 부수적인 코드를 모두 담기에는 코드양이 많아 담지 못했다.
세부적으로 제가 구현해본 전체 코드는 링크에서 확인할 수 있습니다.