한땀한땀

React Query 버전 업그레이드 및 커스텀 타입 제거 본문

FrontEnd Dev

React Query 버전 업그레이드 및 커스텀 타입 제거

junfromkorea 2025. 4. 4. 08:00

지난번 Nextj.js 13에서 15로의 마이그레이션을 진행한데 이어, 이번에는 React Query의 버전 업을 진행하게 되었다. 기존 코드에서는 전임자가 설정한 커스텀 타입이 많아 유지보수성이 낮았고, 공식 문서에서 제공하는 방식을 그대로 활용하기 어렸습니다. 따라서, 이번 기회를 통해 React Query v5로 마이그레이션 하면서 이러한 문제들을 해결하였다.

 

기존 문제점

전임자가 설정한 React Query 관련 커스텀 타입이 존재했으며, 해당 타입에 맞춰 작성된 쿼리 로직은 확장성이 부족했다. 공식 문서를 참고하여 개선하려 해도 useQuery, useMutation 등의 핵심 함수에 대해 글로벌 타입을 선언해버린 탓에 공식 문서대로 사용할 수 없었다.

 

기존 설정에서는:

  • React Query v4 타입을 자체적으로 커스텀하여 공식 문서대로 사용이 불가능
  • 사용법이 명확하지 않아 코드 변경 시 복잡성이 증가
  • 하나의 타입으로 모든 상황을 대응하려고 시도했으나 오히려 확장성과 유지보수성이 떨어짐

이렇게 판단하였고, 이에 따라 기존 커스텀 타입을 제거하고 공식 타입을 사용하도록 개선하는 방향으로 결정했다.

커스텀 타입 제거 과정

기존 커스텀 타입 예시

declare module '@tanstack/react-query' {
  export declare function useQuery<TQueryFnData, TData, TQueryKey extends QueryKey>(
    options: UseQueryOptions<TQueryFnData, AxiosError, TData, TQueryKey>
  ): UseQueryResult<TData, AxiosError>;
  
  
  export declare function useMutation<TData, TVariables, TContext>(
    options: UseMutationOptions<TData, AxiosError, TVariables, TContext>
  ): UseMutationResult<TData, AxiosError<ErrResponse>, TVariables, TContext>;
}

 

위 코드에서 확인할 수 있는 문제점은 다음과 같다:

  • useQuery, useMutation 등에 대한 글로벌 타입이 선언되어 공식 문서에서 제공하는 방식대로 사용이 불가능
  • AxiosError 등 특정 라이브러리에 종속된 타입이 기본값으로 설정됨
  • 불필요한 타입 선언으로 인해 유지보수성이 낮음

개선 과정

개선한 과정을 요약하면 다음과 같다

  • 커스텀 타입 제거
  • 제거 후 발생한 약 1500개의 타입 에러 수정 (에러가 정말 많았는데 스크린 샷 찍는 걸 깜빡했다 ㅠ)
  • 기존 Query Key Factory 방식 대신 queryOptions를 사용한 방식으로 변경

React Query 개선 예시

Query 예시

// 유저관련 리액트 쿼리 개선 예시

// query 예시
export const userQueries = {
  keys: {
    all: () => ['user'],
    info: () => [...userQueries.keys.all(), 'info'],
    membership: () => [...userQueries.keys.info(), 'membership'],
  },

  info: () =>
    queryOptions({
      queryKey: userQueries.keys.info(),
      queryFn: getUserInfo,
      enabled: getIsAuthenticated(),
      select: res => res.data,
    }),

  additionalInfo: ({ userId }: { userId: string | undefined }) =>
    queryOptions({
      queryKey: [...userQueries.keys.info(), 'additional', userId],
      queryFn: () => getUserAdditionalInfo(userId ?? ''),
      enabled: !!userId,
      select: res => res.data,
    }),
};

// mutation 예시
export const useUserUpdateMutation = ({ userId }: { userId?: string }) => {
  return useMutation({
    mutationFn: (data: { userId: string; nickname: string; useEmail: string }) => {
      if (!userId) {
        console.warn('User ID is undefined. Returning a no-op mutation.');
        return Promise.resolve();
      }
      return request.put(`/api/sejong/v1/users/${userId}/simple`, data);
    },
  });
};

 

위와 같이 선언하는 방식을 변경한 후, 실제로 해당 React Query를 활용하는 쪽에서도 여러 장점이 생겼다.

 

 

Query Key Factory를 사용하던 기존 방식

기존 방식에는 Query Key Factory를 사용하여 아래와 같이 쿼리르 정의했으며, 이로 인해 useQuery의 옵션을 유연하게 설정하는 것이 어려웠다.

const investmentProfileQueries = createQueryKeys('mbti', {
  questionList: () => ({
    queryKey: [''],
    queryFn: (): Promise<DefaultResponse<InvestmentProfileQuestion[]>> =>
      request.get(`/api/seoul/v1/cityfolio/investment_profile/questions`),
  }),
});

export const useInvestmentProfileQuestionListQuery = () => {
  return useQuery({
    ...investmentProfileQueries.questionList(),
    select: res => res.data,
  });
};

 

개선 후 방식

기존 방식과 달리, 개선 후에는 queryOptions를 활용하여 useQuery 옵션을 더욱 유연하게 설정할 수 있게 되었다.

예를 들면 기존에는 enabled 옵션을 위해 별도의 인자를 추가하고 조건문을 작성해야 했으나, 이제는 queryOptions를 활용하여 원하는 곳에서 직접 선언이 가능해졌다.

 

const { data: userInfo } = useQuery({
  ...userQueries.info(),
  enabled: isUserLoggedIn(),
});

 

변경 후 장점 요약

  • 유연한 옵션 설정 가능: 기존에는 특정 컴포넌트에서만 적용해야 하는 enabled 옵션을 위해 별도의 인자를 추가하고 조건문을 작성해야 했으나, 이제는 queryOptions를 활용하여 원하는 곳에서 직접 선언 가능
  • 공식 문서와 동일한 사용 방식: 글로벌 커스텀 타입 제거 후, 공식 문서에 나온 방법 그대로 적용 가능하여 학습 비용 감소
  • 확장성 증가: 개별 컴포넌트에서 직접 useQuery를 선언하고 필요한 옵션을 추가할 수 있어 유지보수 및 확장성이 향상됨

 

 

아직 모든 React Query 로직을 개선한 것은 아니지만, 틈틈이 개선하여 DX 개선 및 유지보수를 용이하게 만들어야겠다.