[TOJ] 개발기록 - 커스텀 훅에 useQuery사용, 객체구조분해 할당으로 반환 받기

조민호·2023년 11월 9일
0


동일한 로직은 항상 함수단위로 변경하고, 최대한 함수의 인자로 그 각각의 로직마다 필요한 사항들을 넘겨줘서 상황에 따라 다른 로직으로 재사용 할 수 있도록 하자!


기존 리액트 쿼리 사용 로직은 아래와 같다

  • 문제 관련 페이지들의 레이아웃인 CommonLayoutWithMenus

  • 문제 정보를 보여주는 ProblemInfo

  • 문제 제출 페이지의 Submit

// API 로직
export const getProblemDetail = async <T extends ProblemDetailType>(id: number) => {
  const res = await axios.get<T>(`/api/problem/detail/${id}`);
  return res.data;
};
// useQuery (각 컴포넌트 마다 아래와 같이 선언 후 사용)

const { data } = useQuery({
    queryKey: ['problemInfo', { problemId: Number(problemId) }],
    queryFn: async () => {
      const res = await getProblemDetail<ProblemDetailType>(Number(problemId));
      return res.data;
    },
    staleTime: Infinity,
  });

이걸 리팩토링 해 볼 것이다.



  1. API분리

    axios.create 를 따로 생성하고 (여기서 baseURL은 프록시로

    사용하고 있으므로 여기에 작성할 필요 x)

    // instance.ts
    
    import axios from 'axios';
    
    export const API = axios.create({});

    API를 하나의 객체 메소드로 정의해서 호출한다

    // problem.ts (문제 정보 관련 API 파일)
    
    import axios from 'axios';
    import { API } from './instance';
    import { ProblemDetailType } from '@/type/problem';
    
    // API
    export const problemApi = {
    
      ... ,
    
      getProblemDetail: async <T extends ProblemDetailType>(id: number) => {
        return await API.get<T>(`/api/problem/detail/${id}`);
      },
    
    };
  2. useQurey 분리

    기존에 사용하던 useQurey를 각 컴포넌트에서 그대로

    사용하지 않고 useQurey 자체를 하나의 함수로 분리를 한다

    export const useGetProblemDetail = <T>(problemId?: number | null, options?: T) => {
    
      return useQuery({
        queryKey: ['problemDetail', { problemId }],
        queryFn: async () => {
          const res = await problemApi.getProblemDetail(problemId!);
          return res.data;
        },
        staleTime: Infinity,
        retry: 0,
        enabled: problemId !== null,
      });
    
    };
    • API를 호출할때 사용할 인자

    • enabled 옵션으로 사용할 상태

    • 그 외 option들을 useGetProblemDetail함수의 인자로 받는다

    💡 enabled 옵션도 함수 인자로 넘겨줘도 됨.
    useGetProblemDetail(currentProblemId, {
        enabled: currentProblemId !== null,
      })
  3. usage

    const { data: { data: problemDetailData = null } = {} } = useGetProblemDetail(currentProblemId);

    현재 호출하는 API의 명세를 보면 다음과 같다

    Axios의 반환 객체의 data프로퍼티에 접근한 실데이터인 JSON객체

    에서 , data프로퍼티로 한번 더 접근을 해야 내가 원하는 데이터가 들어온다

    우선 useQuery의 queryFn에서 .data를 반환해줘서

    실데이터인 JSON객체만 반환이 될 것이다

    // 위의 useGetProblemDetail 일부
    
    queryFn: async () => {
          const res = await problemApi.getProblemDetail(problemId!);
          return res.data;
        },

    그리고 이걸 해당 컴포넌트에서 사용해야 하는데

    아래와 같이 data에는 useQuery가 반환하는 데이터로 접근하는

    기본 내장 프로퍼티인 data 변수에 JSON객체가 들어간다

    // data는 useQuery가 반환하는 데이터로 접근하는 프로퍼티
    // JSON객체의 data가 아님
    const { data } = useGetProblemDetail(currentProblemId);

    원래 같으면 아래와 같이 접근을 해야 하지만

    const { data } = useGetProblemDetail(currentProblemId);
    
    console.log(data?.data) 

    그 대신 객체 구조분해 할당을 이용해서 바로 변수에 원하는 데이터를 넣어 줄 수 있다

    { data: { data: problemDetailData = null } = {} }
    1. 객체 구조분해 할당으로 useQuery의 data 안에 있는 data프로퍼티에 접근하고 ,

      이 값을 problemDetailData 로 받는다

      다만, useGetProblemDetail에서 반환하는 값이 undefined일 수도 있다 (비동기 호출 오류 시)

      그러므로 useQuery에서 반환하는 값이 undefined일 수 있다는 것을 대비해서

      undefined 반환 시 = {} 로 초기화 하도록 한다

      { data: { data: problemDetailData = null } = {} }
    2. 추가로, 데이터는 불러와 졌는데 이 JSON 데이터의 data프로퍼티 값이 없거나,

      data프로퍼티 값이 undefined인 경우를 대비해서 = null 로 초기화를 하도록 한다

      { data: { data: problemDetailData = null } = {} }

    위의 2가지 경우를 대비하지 않으면 TS는 자체적으로 에러를 뱉어낸다.



problemDetailData 의 기본 초깃값을 null로 설정 했으므로 앞으로 problemDetailData 를 기반으로 진행하는 조건부 로직에선 undefined가 아닌 null로 비교해야 한다

const { data: { data: problemDetailData = null } = {} } = useGetProblemDetail(currentProblemId, {
    enabled: currentProblemId !== null,
  });

  return (
    <div className={CommonLayoutStyle}>
      { problemDetailData !== null && <ProblemMenus problemDetail={problemDetailData} /> }
      <Outlet />
    </div>
  );
useEffect(() => {
    const getFirstValue = () => {
      if (isStart && **problemDetailData !== null**) {
        const templateStr = problemDetailData.template;
        setIsStart(false);
        setCode(templateStr);
      }
    };
    getFirstValue();
  }, [problemDetailData]);

최종적으로 3개 data 체이닝을 구조분해 할당으로 해결한 것이다


// AxiosResponse의 data , useQuery의 data , JSON내부의 data
     data               . data            . data

사실 .data 체이닝을 해결한 것은 useQuery의 data , JSON내부의 data 2개이다

첫번째 AxiosResponse의 data는 애초에 api를 호출 후, res.data로 반환을 해줬기 때문이다.

profile
웰시코기발바닥

0개의 댓글