Axios 인터셉터 타입스크립트에서 제대로 쓰기

bang9dev·2021년 7월 29일
5

Axios 에는 interceptors 라는 기능이 있다.
이를 통해서 request / response 에 선행,후행 처리를 커스텀하게 할 수 있다.

const client = Axios.create({ baseURL: 'https://my-url.com/v1' });

client.interceptors.response.use(res => {
  return res.data;
})

client.interceptors.request.use(req => {
  req.headers.authorization = TokenManager.accessToken;
  return req;
})

// 원래대로라면, response.data 에 user 가 들어있어야 하지만
// interceptor 를 통해서 response.data 를 건너뛰었다.
const user = await client.get('/users/my-uid');

그취만.. 타입 스크립트에서는 interceptor 를 통해서 한번 포맷팅을 변경했지만
client.get 의 타입은 그대로 response.data 형태로 남아있는걸 겪을 수 있다.

// user 의 타입은 AxiosResponse 로 감싸져서, 실제로는 user 객체를 받아오지만
// 타입상으로는 { data: { name: string; age: number; } } 과 같은 형태로 나온다.
const user = await client.get<{ name:string; age:number; }>('/users/my-uid');

https://github.com/axios/axios/issues/1510 이슈를 통해서 활발한 논의가 진행됐었고
https://github.com/axios/axios/pull/1605 를 통해서 커스텀한 타입을 추가할 수 있게 수정되었다. (뒤쪽 제네릭의 기본값을 AxiosResponse 로 감싼 형태로 처리하고, 해당 제네릭을 return 값으로 처리했다.)

const user = await client.get<any,{ name:string; age:number; }>('/users/my-uid');

흠.. 계속해서 사용하기에 적합한 방식은 아닌 것 같다는 생각이 들었다.


typescript 는 타입에 대해 여러가지 방법을 지원하는데 이 중 인터페이스와 상속을 사용하면 쉽게 해결할 수 있다.

interface CustomInstance extends AxiosInstance

짜잔.. 바로 인터페이스를 상속받은 뒤, 재정의해서 쓰면 된다. (물론 Axios namespace 를 사용할수도 있지만 오염시키지는 말자.)
아래의 예제는, 기본으로 내려주는 형태가 정해진 경우를 가정해서 작성했다.
타입의 근본적인 형태만 일치한다면, Instance 자체에 Generic 을 할당하거나
얼마든지 원하는 형태로 가공해서 사용할 수 있다.


type CustomResponseFormat<T = any> = {
  response: T;
  refreshedToken?: string;
}
interface CustomInstance extends AxiosInstance {
    interceptors: {
        request: AxiosInterceptorManager<AxiosRequestConfig>;
        response: AxiosInterceptorManager<AxiosResponse<CustomResponseFormat>>;
    };
    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>;
}

const client:CustomInstance = Axios.create({ baseURL: 'https://my-url.com/v1' });

// res - AxiosResponse<CustomResponseFormat>
client.interceptors.response.use(res => {
  if(res.data.refreshedToken) TokenManager.replace(refreshedToken);
  return res.data.response;
})

client.interceptors.request.use(request => {
  request.headers.authorization = TokenManager.accessToken;
  return request;
})

// user - { name:string; age:number; }
const user = await client.get<{ name:string; age:number; }>('/users/my-uid');

타입을 잘 다루는 분이라면, AxiosInterceptorManager 도 재정의하고, infer 혹은 predicates 를 통해서 interceptor 에서 리턴한 타입을 자동으로 추론되게 할 수 있지 않을까 하는 생각도 해본다.

끝.

0개의 댓글