현재 프로젝트에는 전역적으로 사용하는 axios 인스턴스를 만들고, 해당 인스턴스에 request/ response 의 인터셉터를 추가한 뒤, api 호출 부분을 요청 별 함수를 만들어 export 하도록 구성되어 있다.
현재로서는 개발할 때 큰 문제점은 없었기 때문에 이런 형태의 구성으로 개발은 끝마쳤지만 의문점이 두가지 생겼다.
1. axios 인스턴스는 꼭 전역으로 애플리케이션 전체 생명주기 동안 유지되어야만 하는가?
2. 도메인별로 인터셉터 로직이 달라져야한다면 또 다른 전역적으로 유지되는 axios 인스턴스를 만들어야하는가?
이 두가지 의문점을 개선해보기 위해 axios 인스턴스를 감싸는 클래스를 만들어보기로 했다.
1. 클래스로 instance를 감싸고 생성자 함수에서 인스턴스를 create 한다.
2. 공통적으로 사용되는 interceptor 로직을 부모 클래스에서 선언하고 각 도메인에서 추가적으로 필요한 interceptor 로직은 추상메소드로 자식 클래스에서 직접 구현하도록 만든다.
3. 각 도메인별 자식클래스들을 api 요청이 필요한 곳에서 해당 클래스의 인스턴스를 생성해서 사용한다.
실제 코드는 다음과 같다
BaseHttpClient.ts
/* eslint-disable no-param-reassign */
/* eslint-disable class-methods-use-this */
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import { config } from 'config'
import { camelizeKeys, decamelizeKeys } from 'humps'
import { history } from 'index'
import LOCATIONS from 'utils/constants/locations'
export abstract class BaseHttpClient {
protected readonly instance: AxiosInstance
public constructor() {
if (!config.API_HOST) throw new Error('environment valuable not set : API_HOST')
this.instance = axios.create({ baseURL: config.API_HOST })
this.initializeResponseInterceptor()
this.initializeRequestInterceptor()
}
private initializeRequestInterceptor = () => {
this.instance.interceptors.request.use(this.handleRequestFulfilled)
}
private initializeResponseInterceptor = () => {
this.instance.interceptors.response.use(this.handleResponseFulfilled, this.handleResponseRejected)
}
private handleResponseFulfilled = ({ data }: AxiosResponse) => camelizeKeys(data)
private handleResponseRejected = (error: AxiosError) => {
(...)
return Promise.reject(error)
}
protected handleRequestFulfilled = (axiosRequestConfig: AxiosRequestConfig) => {
const requestConfig = { ...axiosRequestConfig }
(...)
return requestConfig
}
/**
* sub class 에서 extraRequestInterceptor 를 구현했을 때
* axios instance 에 extraRequestInterceptor 를 할당해주는 method
* extraRequestInterceptor를 추가할 경우 sub class 의 생성자에서 호출
*/
protected addRequestInterceptor = () => {
this.instance.interceptors.request.use(this.extraRequestInterceptor)
}
/**
* 추가 request interceptor 정의
*/
protected abstract extraRequestInterceptor(axiosRequestConfig: AxiosRequestConfig): AxiosRequestConfig
}
AuthorizedHttpClient
/* eslint-disable class-methods-use-this */
import { AxiosRequestConfig } from 'axios'
import { AuthStorage } from 'services/storages'
import { BaseHttpClient } from './BaseHttpClient'
export class AuthorizedHttpClient extends BaseHttpClient {
public constructor() {
super()
// BaseHttpClient 클래스에 protected 로 정의된 addRequestInterceptor를 호출하여
// 이 클래스에서 정의한 extraRequestInterceptor를 추가 interceptor로 등록한다.
this.addRequestInterceptor()
}
protected extraRequestInterceptor(axiosRequestConfig: AxiosRequestConfig<unknown>): AxiosRequestConfig<unknown> {
const requestConfig = { ...axiosRequestConfig, headers: axiosRequestConfig.headers || {} }
requestConfig.headers.Authorization = `Bearer ${AuthStorage.get()}`
return requestConfig
}
}
ContentHttpClient
import { ListRequestParams } from 'apis/apis'
import { AuthorizedHttpClient } from 'apis/base'
import { GetContentOverviews } from 'types/contents'
export class ContentHttpClient extends AuthorizedHttpClient {
private END_POINT = '/contents' as const
public getContentOverviews: GetContentOverviews = params => this.instance.get(this.END_POINT, { params })
}
사용 예시
const contentClient = new ContentHttpClient()
const res = await contentClient.getContentOverviews(listRequestParam)
- 이 같은 구조를 사용하면 해당 클래스를 만든 스코프 안에서만 axios 인스턴스가 생성되고 사라진다. 그리고 각 도메인별로 필요한 interceptor를 쉽게 재정의 할 수 있다.
- 다만 필요할 때마다 axios 인스턴스를 새로 만드는 방식이 더 올바른 방식인지, 그냥 전역적으로 axios 인스턴스를 만들어놓고 사용하는 방식이 올바른 방식인지에 대한 의문이 남아있다.
'FrontEnd > react' 카테고리의 다른 글
React 프로젝트 구조화 2 - atomic 디자인 적용의 한계와 반성 (0) | 2022.09.26 |
---|---|
React.forwardRef with typescript generics (0) | 2022.09.20 |
Typescript, MUI (Material UI v5), React hook form 함께 사용하기 (with Generic) (2/2) (0) | 2022.09.04 |
Typescript, MUI (Material UI v5), React hook form 함께 사용하기 (with Generic) (1/2) (0) | 2022.09.03 |
CRA 없이 React 프로젝트 구성하기 (0) | 2022.06.25 |