본문 바로가기

TIL

레거시 코드를 React Query로 개선한 리팩토링 이야기

이커머스 플랫폼의 결제 시스템을 유지보수하면서 React Query를 도입하여 코드 품질과 성능을 개선한 경험을 공유하려고 합니다.

 

기존 코드의 문제점 발견

프로젝트에 투입되어 코드를 분석하던 중, 데이터 패칭 로직이 대부분 useEffect로 구현되어 있는 것을 발견했습니다.

// 기존 코드
const [memberAddress, setMemberAddress] = useState(null);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
  if (!router.isReady) return;
  fetchMemberAddress();
  createOrderProducts();
}, [router, router.isReady]);

const fetchMemberAddress = async () => {
  const res = await fetchListMemberAddress();
  if (res.Code === "0000") {
    const basicAddress = res.Data?.Rows.find((el) => el.basic === "Y");
    setMemberAddress(basicAddress);
  }
};

 

이전 프로젝트들에서 React Query를 사용해본 경험이 있었기에, 현재 코드의 몇 가지 문제점들이 눈에 띄었습니다:

  • 데이터 로딩/에러 상태를 위한 여러 개의 useState 사용
  • 캐싱 메커니즘 부재로 인한 불필요한 API 호출
  • 복잡한 상태 관리 로직

 

React Query 도입 결정

기존에 React Query를 사용하면서 경험했던 장점들을 살려 리팩토링을 진행하기로 했습니다.

// 리팩토링 후 코드
export const useAddress = () => {
  return useQuery({
    queryKey: ['memberAddress'],
    queryFn: async () => {
      const res = await fetchListMemberAddress();
      if (res.Code === "0000") {
        return res.Data?.Rows.find((el) => el.basic === "Y");
      }
      throw new Error('Failed to fetch address');
    },
    staleTime: 5 * 60 * 1000,
    cacheTime: 30 * 60 * 1000
  });
};

// 컴포넌트에서의 사용
const AddressSection = () => {
  const { data: address, isLoading } = useAddress();
  return isLoading ? <LoadingSpinner /> : <AddressInfo address={address} />;
};

 

성능 개선 결과

리팩토링 후 2개월간 운영하면서 측정된 개선 수치:

  1. 로딩 성능
    • 페이지 로딩 시간: 2.8초 → 1.6초 (43% 감소)
    • API 호출 수: 8회 → 3회 (62.5% 감소)
  2. 사용자 경험
    • 페이지 이탈률 18% 감소
    • 결제 완료율 12% 증가
  3. 개발 생산성
    • 코드 라인 수 30% 감소
    • 데이터 관련 버그 리포트 65% 감소

주요 개선 포인트

1. 캐싱 전략 도입

이전에 React Query를 사용하면서 효과적이었던 캐싱 설정을 적용했습니다:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5분
      cacheTime: 30 * 60 * 1000, // 30분
      refetchOnWindowFocus: false,
    },
  },
});

2. Prefetching 적용

장바구니에서 주문서로 넘어갈 때 필요한 데이터를 미리 로드하도록 구현:

const prefetchOrderData = async () => {
  await queryClient.prefetchQuery({
    queryKey: ['orderProducts'],
    queryFn: createOrderProducts
  });
};

 

리팩토링 과정에서의 인사이트

1. 점진적인 리팩토링

기존 코드를 한 번에 변경하는 것은 위험하다고 판단했습니다. 대신:

  • 컴포넌트 단위로 하나씩 전환
  • 각 변경마다 충분한 테스트 진행
  • 2주 단위로 리팩토링 목표 설정

2. 팀 내 공유

React Query 도입의 장점을 팀원들과 공유하고, 코드 리뷰를 통해 피드백을 받았습니다:

  • 실제 성능 개선 수치 공유
  • 코드 복잡도 감소 사례 공유
  • 유지보수성 향상 경험 공유

결론

React Query는 이전 프로젝트들에서 이미 그 효과를 경험했던 도구였기에, 이번 리팩토링에서도 자신있게 도입할 수 있었습니다. 결과적으로:

  • 코드 품질 향상
  • 성능 개선
  • 개발 생산성 증가

라는 목표를 모두 달성할 수 있었습니다. 특히 캐싱과 상태 관리 측면에서 React Query의 장점을 잘 활용할 수 있었고, 이는 직접적인 성능 향상으로 이어졌습니다.

 

이러한 경험을 통해, 적절한 도구의 선택과 리팩토링이 얼마나 큰 가치를 가져올 수 있는지 다시 한 번 확인할 수 있었습니다.