Axios Interceptor Customizing

MyeonghoonNam·2022년 12월 29일
0

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에 관하여 커스터마이징을 진행해보았다.
커스터마이징을 진행한 후 응답 객체의 데이터의 타입추론이 이루어지는 것 을 볼 수 있다.

글을 작성할 때 다른 부수적인 코드를 모두 담기에는 코드양이 많아 담지 못했다.

세부적으로 제가 구현해본 전체 코드는 링크에서 확인할 수 있습니다.

profile
꾸준히 성장하는 개발자를 목표로 합니다.

0개의 댓글