본문 바로가기

코드스테이츠 SEB FE 41기/Main-Project(MatP)

[react] useAxios 커스텀 훅 사용기1(feat. typescript)

반응형

네트워크 통신이 많은 앱을 만들다보니 실시간 데이터 리로딩 로직의 필요성을 느끼게 되었고 더 이상 무수한 get 요청에 의존할 수 없다는 것을 깨달았다... 또한 네트워크 관련 코드들을 정리하여 관심사 분리를 진행해야겠다는 생각이 들었다. 코드가 너무 길고 지저분해지면 클린한 코드가 아니라는 생각이 들었기 때문이다. 작은 프로젝트지만 그래도 최대한 클린코드를 향해 나아가는(?) 마음가짐을 가지고 싶었다 :)

해당 문제를 어떻게 해결할지 팀원 분과 고민을 많이 하다가 멘토님께 조언을 얻기로 하고 질문 목록을 정리하여 미팅에 참석하였다!

멘토님께서 추천해주신 방법은 커스텀 훅을 만들어 네트워크 통신 후 response를 받아오도록 만들고, 네트워크 통신과 관련된 코드는 도메인 별로 나누어 파일로 정리하는 것이였다.

 

이제 리팩토링을 해보자! 

작성한 useAxios 코드는 이렇다.

callback 함수에는 원하는 네트워크 통신 관련 함수를 넣어주면 된다(GET, POST, PATCH, DELETE).

deps는 종속성 배열이다. axiosData 함수가 실행될 조건들을 넣어준다. 

skip에는 false/true 두 가지 조건이 있는데, false이면 컴포넌트가 렌더링 되자마자 실행되고 true이면 컴포넌트가 렌더링이 되어도 실행되지 않는다. skip을 true로 두면 POST나 PATCH등 네트워크 통신 함수가 실행되기를 원하는 타이밍에(종속성 배열에 있는 변수 값 변경 등) axiosData가 실행되도록 할 수 있다.

import { useState, useEffect, useCallback } from "react";

type Status = "Idle" | "Loading" | "Success" | "Error";
interface UseAxiosReturn<T> {
  axiosData: () => void;
  responseData: T | null;
  status: Status;
}

const useAxios = <T = any>(
  callback: () => Promise<T>,
  deps: any[] = [],
  skip = false
): UseAxiosReturn<T> => {
  const [responseData, setResponseData] = useState<T | null>(null);
  const [status, setStatus] = useState<Status>("Idle");

  const axiosData = useCallback(async () => {
    setStatus("Loading");
    try {
      const data = await callback();
      setResponseData(data);
      setStatus("Success");
    } catch (error) {
      setStatus("Error");
      throw error;
    }
  }, deps);

  useEffect(() => {
    if (skip) return;
    axiosData();
  }, deps);

  return { axiosData, responseData, status };
};

export default useAxios;

이제 useAxios 커스텀 훅을 사용해보자. 먼저 네트워크 관련 함수들을 만들어준 뒤, 같은 도메인 별로 모아준다.

export const getPosts = async () => {
  const response = await axios.get(`${url}/posts`);
  return response.data;
};

사용하고 싶은 컴포넌트에서 구조분해할당으로 responseData를 선언하여 사용한다면 원하는 데이터를 얻을 수 있다.

const { responseData } = useAxios(getPosts, [], false);

위의 경우에는 skip 값이 false로 설정되어 컴포넌트가 렌더링 되자마자 GET 요청이 실행된다.

하지만 POST, PATCH, DELETE의 경우에는 사용 방법이 다르다.

컴포넌트가 렌더링 되자마자 실행되는 것이 아닌, 내가 원할 떄 실행될 수 있도록 만들어줘야 한다.

const { axiosData } = useAxios(
    () =>
      updatePost(
        newTitle,
        htmlContent,
        createdAt,
        clicked.filter(Boolean).length,
        thumbnailUrl,
        Number(id)
      ),
    [newTitle, htmlContent, createdAt, clicked, thumbnailUrl],
    true
  );

이 처럼 deps 배열에 axiosData가 실행되기를 원하는 조건인 변수를 넣어주고 skip에 true를 주면 특정한 조건에 네트워크 요청이 이루어질 수 있도록 할 수 있다. 즉, deps 배열에 포함되어 있는 변수의 값이 바뀌면 axiosData 함수가 실행되어 내가 의도한 타이밍 및 조건에 POST, PATCH 요청을 보낼 수 있다. 이 방법과 관련된 자세한 예시는 2탄에서 다뤄보는 걸로!

 

마치며, 

GET 요청은 실행 조건이 따로 필요 없으니 밑의 블로그만 보고 쉽게 처리했는데, GET이 아닌 다른 요청들을 처리해주는 방법을 몰라 퍽 난감했었다. 내가 스스로 헤쳐나가야 하는 난관이였다. 그래도 본질? 기본?을 붙들고 늘어지니 반나절 안에 해결 방법이 떠오르지 않았나 싶다. 역시 잘 모르겠으면 기본을 돌아보며 기초에 충실해야 하나보다.

이번 기회를 통해 네트워크 요청 관련 코드를 한번 싹 정리 후 리팩토링하니 후련하다! 원하던 대로 관심사 분리를 통해 코드도 간결해지고 네트워크 통신 코드와 UI 관련 코드가 분리되어 내가 보기에도 좋고 다른 팀원들에게 공유하며 협업할 때에도 훨씬 수월하게 진행되었던 것 같다. 관심사 분리의 중요성을 다시 한번 깨닫게 되었다. 

추가로, 

멘토님이 말씀하신 대로 interface 코드들도 사용되는 도메인 별로 구분하여 API 문서화 과정에서 함께 넣어주고, 이를 export 하여 여기저기서 원할 때 꺼내쓸 수 있도록 만들었다.

export interface IPosts {
  postId: number;
  likes: number;
  commentcount: number;
  thumbnail_url: string;
}

 

더 이상 get 요청 남발은 없다 후후 무수한 get들아 안녕이다~~

 

참고한 블로그)

https://react.vlpt.us/integrate-api/03-useAsync.html

 

3. useAsync 커스텀 Hook 만들어서 사용하기 · GitBook

3. useAsync 커스텀 Hook 만들어서 사용하기 데이터를 요청해야 할 때마다 리듀서를 작성하는 것은 번거로운 일 입니다. 매번 반복되는 코드를 작성하는 대신에, 커스텀 Hook 을 만들어서 요청 상태

react.vlpt.us

 

반응형