[1주차] api 리팩토링

cansweep·2022년 8월 15일
0

리팩토링 전

const createTodos = async (data: TodoValues) => {
  const res = await fetch(`${process.env.REACT_APP_SERVER_URL}/todos`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `${localStorage.getItem("token")}`,
    },
    body: JSON.stringify(data),
  });
  const result: Response = await res.json();
  return result;
};

function Form({ onClose, selectedTodo }: FormProps) {
  const createMutation = useMutation(createTodos, {
    onSuccess: (data) => {
      onClose();
      alert("todo를 추가했습니다.");
    },
    onError: (err) => {
      console.log(err);
    },
  });
  
  return (
    //...
  );
}

위의 코드와 같이 react-query를 사용할 때 handler 함수를 파일 내에 작성했다.

대신 컴포넌트 선언 밖에 두어 리렌더링 시 새로 생성되는 것을 방지했고 useQuery나 useMutation 사용 시 query함수를 간략하게 작성할 수 있었다. (여기서는 이름만 적으면 되니까!)

하지만 엘리스 AI 트랙 3차 때도, 지금 궁금해약 프로젝트에서도 동일한 고민을 했었는데, 바로 fetch 문을 일일이 적기 귀찮다는 것이다.

그래서 fetch wrapper를 사용할까 생각도 했지만 프로젝트 들어가기 전 배워야 할 것들이 많아 당장 도입하는 건 포기했었다.

이번 사전 과제에서도 동일하게 코딩했다가 1주차 수업을 듣고 api를 개선해야 할 필요를 느꼈다.

리팩토링 후

api.ts

api.ts를 만들기 전 내 고민은 두 가지였다.

1. fetch문 중복 줄이기
2. response마다 다른 형태를 가지고 있는데 result를 어떻게 반환할까?

사실 이 내용을 사전 과제에 적용하기 전 궁금해약 프로젝트에 먼저 도입했다. (아마 나중에 적을 예정)
그래서 2번 내용이 제일 큰 고민이었다.

사전 과제에서는 에러처리하는 부분을 모두 생략했지만 실제 프로젝트를 진행하다보면 fetch 요청에서 생기는 에러가 아닌 백엔드가 에러를 반환해주는 경우가 있다.
예를 들어 이메일로 유저를 찾고자 하지만 해당하는 이메일이 없을 때 말이다.

axios를 사용하면 이런 부분까지 모두 잡아주지만 fetch는 그렇지 않다.
fetch의 입장에서는 아무튼 백엔드와 api 요청이 원활히 이루어진 것이다.

그래서 res.json()의 결과가 필요했고 이를 통해 에러처리를 하거나 응답 결과를 사용하기 위해 타입을 선언할 필요가 있었다.

const baseUrl = process.env.REACT_APP_SERVER_URL;

const get = async <T>(endpoint: string) => {
  const url = baseUrl + endpoint;
  const res = await fetch(url, {
    headers: { Authorization: `${localStorage.getItem("token")}` },
  });
  const result: T = await res.json();
  return result;
};

const post = async <T, U>(endpoint: string, data: U) => {
  const url = baseUrl + endpoint;
  const res = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `${localStorage.getItem("token")}`,
    },
    body: JSON.stringify(data),
  });
  const result: T = await res.json();
  return result;
};

export { get, post };

실제 구현한 내용에는 get, post, put, delete가 모두 있으나 블로그에 적기에는 너무 길어 get과 post만 가져왔다.

제네릭 타입을 사용해 응답 결과인 result나 body로 보낼 data에 타입을 선언해 주었다.
result에 타입을 선언해 줌으로써 리턴된 객체 안의 값에 바로 접근해 사용할 수 있다.

적용 예시

import * as Api from "../../api/api";

function Form({ onClose, selectedTodo }: FormProps) {
  const createMutation = useMutation(
    (data: TodoValues) => Api.post<Response, TodoValues>("/todos", data),
    {
      onSuccess: (data) => {
        onClose();
        alert("todo를 추가했습니다.");
      },
      onError: (err) => {
        console.log(err);
      },
    },
  );
  
    return (
    //...
  );
}

더이상 핸들러 함수를 따로 선언할 필요가 없어졌다.
fetch문도 반복해 적지 않아도 된다.
그동안 headers 내용 적느라 힘들었는데 이렇게 구현하니 한결 편해졌다.

위의 예시에는 반환된 result의 값을 사용하고 있지 않지만 또 이런 식으로 바로 리턴된 결과를 사용할 수 있다.

 useQuery("getTodos", () => Api.get<TodosResponse>("/todos"), {
    enabled: !!localStorage.getItem("token"),
    onSuccess: ({ data }) => {
      setTodos(data);
    },
  });
profile
하고 싶은 건 다 해보자! 를 달고 사는 프론트엔드 개발자입니다.

0개의 댓글