Custom Type 만들기 w. Axios

정(JJeong)·2024년 8월 26일
0
post-thumbnail

현재 회사에 FE 시니어나 사수가 없다. 입사 때부터 그랬다.
그렇다보니 TS를 아시는 분이 나를 제외하곤 없었다..

난감했지만 도입 했을 경우 장점에 대해서 어필하며 차츰 적용시켰다. 그리고 최근 들어간 신규 프로젝트에선 일부 tool을 제외하곤 TS를 기본으로 하고 있다.

이 과정에서 부족하지만 동료분들에게 TS를 알려드리며 나도 다시금 TS를 익히고 있는데 이번에 부득이하게 Axios를 사용하며 custom type을 만들 필요성이 생겼다.


Axios 호출문 작성

나는 api와 관련된 코드들을 관련된 그룹으로 묶어 객체화 하는 것을 좋아한다.
이것이 관리/사용 측면에서 좋다고 느끼기 때문이다.

일부 측면에선 캡슐화라고 할 수도 있을 것 같다.

import apiClient from ".";
import AxiosResponse from "axios";
import * as P from "@/@types/feed/params";
import * as R from "@/@types/feed/response";

export const feedApi = {
  saveContents: async (params: P.SaveContentsParam) => {
    const res = await apiClient.post<
      R.SaveContentsResponse,
      AxiosResponse<R.SaveContentsResponse>
    >("endpoint/save", params);

    return res;
  },
  
  // ===== 생략
};
  • axios 호출 함수 리턴 타입으로 지정할 AxiosResponse 내장 타입 import
  • "@/@types/feed/params";"@/@types/feed/response"에서 각각의 api에 해당하는 param type과 response type을 작성 및 import

여기에서 axios 메소드의 제네릭 지정 배경을 설명하자면 다음과 같다.

post 메소드의 제네릭 타입

- 이는 `get`, `delete`, `put` 등도 동일한 구조이다.
post<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
  • 세 가지의 제네릭
    • T: 첫번째 제네릭이자 AxiosResponse의 제네릭으로 다시 들어간다.
    • R: AxiosResponse타입을 기본으로 하며 제네릭으로 T를 품는다.
    • D: 이는 Axios의 config 타입의 제네릭으로 사용된다.

AxiosResponse의 제네릭 타입

export interface AxiosResponse<T = any, D = any> {
  data: T;
  status: number;
  statusText: string;
  headers: RawAxiosResponseHeaders | AxiosResponseHeaders;
  config: InternalAxiosRequestConfig<D>;
  request?: any;
}

위 구조를 보면 알 수 있다시피 T는 data 즉, response로 들어올 대상 데이터의 타입이고, D는 config type의 제네릭으로 사용된다.

그런데 이 과정에서 return 타입에 axios의 내장 타입을 읽어내고 사용하는데 에러가 발생했다.

왜? Why?



Custom Type의 필요성

위에서 발생 에러의 이유는 이러하다. AxiosResponse를 통해 return 타입을 지정하게 되는데, 이때 type의 구조는 앞서 언급한 대로 다음 key값을 지닌다.

  • data
  • status
  • statusText
  • headers
  • config
  • request

이때 내가 실 데이터에 접근하고자 한다면 위 key 구조에 의거해 data에 접근해야 한다.
근데 문제는 우리 쪽 서비스에서는 저 구조를 따르지 않는다...

우리 서비스에서 나한테 주는 구조는 다음과 같다.

interface OurResponseType {
  contents: object; // data object 구조;
  result: boolean;
  resultMsg: 'OK' | 'NG';

보이는 바와 같이 우리의 key는 data가 아니다. 근데 더 문제는 api마다 저 key값이 다 다르다.
예를 들면 카데고리는 category라고 되어 있다...

때문에 data로는 접근하지 못하고 contents로 접근하고자 하면 AxiosResponse에서는 저 구조를 몰라서 type error를 뱉게 되는 것.

const saveFeedContents = (params: SaveContentsParam) => {
  feedApi.saveContents(params).then((res) => {
    const data = res.contents // ** 접근 불가능
  })
}

type 안정성을 유지하면서 우리가 원하는 key값으로 데이터에 접근할 수 있어야 한다.

그래서 필요했다. Custom Type



Custom Axios Type 작성

짜게된 구조는 간단하다. 제네릭을 별도 처리하게 되는데 다음과 같다.

import axios, { AxiosResponse as OriginalAxiosResponse } from "axios";

export type CustomAxiosResponse<T = any, D = any> = OriginalAxiosResponse<
  T,
  D
> &
  T;

두 가지 제네릭을 받는건 동일하다. AxiosResponse의 기본 구조를 유지해야하니.
근데 여기서 데이터 구조로써 받았던 T를 전체 구조에 합쳐 주는 것이다.

이를 실 적용하면 그저 AxiosResponse라는 타입 대신 CustomAxiosType을 import해서 대체해주는 것으로 refactoring이 간단히 끝나지만 원하는 결과를 얻을 수 있다.

바로 axios 호출문의 결과에서 내가 원하는 key(정확히는 내가 선언해둔 key)에 접근할 수 있는 것.

그래서 위를 적용한 코드는 다음과 같고, return에서는 AxiosResponse의 기본 key값들 대신 내가 T 제네릭으로 넘겨준 타입의 key에 접근할 수 있다.

적용

import apiClient, { CustomAxiosResponse } from ".";
import * as P from "@/@types/feed/params";
import * as R from "@/@types/feed/response";

export const feedApi = {
  saveContents: async (params: P.SaveContentsParam) => {
    const res = await apiClient.post<
      R.SaveContentsResponse,
      CustomAxiosResponse<R.SaveContentsResponse>
    >("/endpoint/save", params);

    return res;
  },
  
  // ===== 생략
}
  • D 제네릭은 config에 대한 것으로 불필요해 넘기지 않았다.

사용

const saveFeedContents = (params: SaveContentsParam) => {
  feedApi.saveContents(params).then((res) => {
    const data = res.contents // ** 접근 가능
  })
}

이렇게 해서 우리 서비스의 api 리턴 구조에 맞춰 접근할 수 있도록 구성하였다.
이 Custom Type을 기본으로 하여 다른 api에도 적용했기 때문에 data에 접근하고자 하는 key 이름이 contents던 category던 상관없이 커버 가능해졌다.



마무리

내가 백엔드 전문 담당이 아니고, 그쪽에 대한 배경이 부족하기 때문에 저런 식으로 구조를 리턴하는게 맞는지 틀린지 모르겠다.

그저 axios에서 default처럼 지정해둔 타입이 저 구조라면 백엔드의 REST api의 구조도 저걸 따르는게 기본인게 아닌가?하는 의문 정도가 남는다.

하지만 뭐가 맞든 틀리든 우리 회사 구조가 저렇다면 맞춰 쓸 줄 알아야지!

그래서 Custom Type을 작성해보았고, 이를 통해 다른 동료분들에게 리뷰 및 적용 안내를 해 type 안정성 및 DX 효율을 "나름" 높였다고 생각이 든다.

나름,, 정말 나름,, 사수기 없는 1년 갓 넘어간 짜바리는 웁니다..

profile
2년차 응애 FE 개발자입니다👶

0개의 댓글