Archive

뒤로가기

프론트엔드

React Query로 데이터 처리의 효율성 높이기

2025년 1월 20일 18:40

React Query를 왜 사용해야 하는가?

사실, React Query 사용 전에는 서버 담당자가 자꾸 React Query를 쓰세요 라고 권장하는 이유를 잘 이해하지 못했습니다. 난 함수 호출 잘 하고있는데? 굳이 배워야해? 라는 마인드였죠

하지만 블로그 코드를 리팩토링하고 뜯어보면서, account 정보를 불러오는 함수를 너무 많이 호출한다는 사실을 깨달았습니다. (이 블로그 왼쪽에 위치한 그 유저 정보 맞습니다.)

그러면서 그냥 유저 정보를 Recoil로 담아서 전역으로 관리할까? 라는 생각도 들었습니다. 하지만 유저 정보가 바뀌었을 때, 또 다른 곳에서 이 함수를 호출해야 한다는 문제가 생깁니다.

그리고 또 고민이 생겼습니다. 지금 계속 호출하는 게 서버에 괜찮나? 현재는 나 혼자만 사용하지만, 더 많은 사용자가 들어오게 된다면 서버에 큰 부담이 될 것이라는 생각이 들었습니다.

이렇게 고민하던 중에, React Query가 제공하는 자동 캐싱과 최신 데이터 요청 기능이 문제를 해결해줄 수 있다는 것을 알게 되었습니다.

React Query란?

React Query는 데이터 페칭과 상태 관리를 매우 효율적으로 처리할 수 있는 라이브러리입니다. React Query는 서버에서 데이터를 가져오는 작업을 간편하게 만들어주며, 비동기 작업을 처리하고 그 결과를 캐싱하여 성능을 최적화 합니다. 주요 특징은 다음과 같습니다.

  1. 자동 캐싱 및 재사용: 동일한 데이터 요청이 있을 경우, 이전에 받은 데이터를 재사용하여 네트워크 요청을 최소화합니다.

  2. 자동 갱신: 데이터가 변경되면, 자동으로 최신 데이터를 가져옵니다.

  3. 로딩, 오류 및 데이터 상태 관리: 로딩 상태, 오류 상태 등을 관리할 수 있는 기능을 제공하여 상태 관리가 간편해집니다.

  4. 쿼리 무효화 및 재요청: 데이터를 수정한 후, 쿼리를 무효화하여 최신 데이터를 다시 요청할 수 있습니다.

여태까지 내가 계속 해왔던 방법 - 함수 계속 호출하기

리액트 프로젝트에서 데이터를 처리할 때, useEffect 안에서 함수를 계속 호출하는 방식을 사용했습니다.

import getAccount from "Main/services/getAccount.service";
import { UserInfoInterface } from "Main/types/Main.type";
import { useEffect, useState } from "react";

export function useAccount() {
  const [userInfo, setUserInfo] = useState<UserInfoInterface | null>(null);

  const fetchAccount = async () => {
    const result = await getAccount();
    if (result.result) {
      setUserInfo(result.profileResult);
    } else {
      alert("사용자 정보를 불러오지 못했습니다.");
    }
  };

  useEffect(() => {
    fetchAccount();
  }, []);

  return userInfo;
}

이 방식은 간단하지만, 몇 가지 단점이 존재합니다:

  1. 반복되는 코드: 데이터 요청이 여러 곳에서 반복되어 코드 중복이 발생합니다.

  2. 로딩 상태와 에러 처리: loading, error 상태를 일일이 관리해야 하므로 코드가 복잡해집니다.

  3. 성능 문제: 데이터 요청이 자주 발생하면 불필요한 네트워크 요청이 많아져 성능에 영향을 줄 수 있습니다.

React Query로 바꿔보자

import { useQuery } from "@tanstack/react-query";
import getAccount from "Main/services/getAccount.service";
import { UserInfoInterface } from "Main/types/Main.type";

export function useFetchUser() {
  const {
    data: userInfo,
    error,
    isLoading,
    refetch,
  } = useQuery({
    queryKey: ["userInfo"],
    queryFn: async () => {
      const result = await getAccount();

      if (result.result) {
        return result.profileResult as UserInfoInterface;
      } else {
        throw new Error("Failed to fetch user information.");
      }
    },
    staleTime: 10 * 60 * 1000,
    cacheTime: 20 * 60 * 1000,
    onError: (error: Error) => {
      alert("사용자 정보를 불러오지 못했습니다.");
    },
  });

  return { userInfo, error, isLoading, refetch };
}

위 코드는 React Query의 useQuery 훅을 사용하여 유저 정보를 서버에서 가져오는 함수입니다. 이 코드는 자동 캐싱과 데이터 갱신 기능을 통해 데이터를 처리합니다.

  1. 쿼리 키와 데이터 요청

    useQuery는 queryKey를 통해 요청을 구분하고, queryFn을 통해 데이터를 요청합니다.

    queryKey:해당 데이터 요청을 고유하게 식별

    queryFn: 실제로 데이터를 요청하는 함수

  2. 캐시 관리

    staleTime: 데이터가 오래된 것으로 간주되기 전에 데이터를 얼마나 유효하게 유지할지를 결정

    cacheTime: 데이터가 캐시에서 삭제되기까지의 시간을 설정

  3. 에러 처리 및 로딩 상태

    onError: 데이터 요청 중 오류가 발생했을 때 호출

    isLoading: 요청 중 로딩 상태

    error: 요청 실패 시의 에러 정보

  4. refetch

    refetch: 데이터를 수동으로 다시 요청할 수 있는 함수

그래서 React Query가 뭐가 다른데?

이 질문이 나온 이유는, React Query를 사용해도 새로고침을 하면 다시 fetch가 된다는 점에서 의문이 발생했기 때문입니다.

새로고침만 해도 쿼리가 다시 실행되는데 그러면 내가 모든 컴포넌트에 account 함수 호출하는거랑 뭐가 달라?

제가 의문이 생겨서 chatGPT에게 실제로 물어봤던 내용입니다.

기존 방식에서는 페이지를 새로고침할 때마다 데이터를 새로 요청하게 되며, 이를 계속 함수 호출이라고 표현할 수 있습니다. 이 방식은 사용자가 새로고침을 할 때마다 매번 API 요청을 보내고, 그에 따른 로딩 상태가 표시되기 때문에 불편할 수 있습니다. 또, 같은 데이터를 반복적으로 요청하게 되어 성능에도 영향을 미칩니다.

반면, React Query는 자동 캐싱 기능을 제공하여, 한 번 데이터를 요청하고 나면 캐시에서 데이터를 가져옵니다. 이후에는 서버에 다시 요청을 보내지 않고, 이미 받은 데이터를 사용할 수 있기 때문에 불필요한 요청을 줄일 수 있습니다. 그리고 데이터가 수정되거나 갱신될 때는 invalidateQueriesrefetchQueries를 통해 필요한 시점에만 다시 요청을 보내어 최신 상태로 동기화할 수 있습니다.

차이점 1: 데이터 요청 및 캐시 관리

• 기존 방식: 페이지 새로 고침 시마다 데이터를 새로 요청.
• React Query: 데이터가 캐시되어, 동일한 데이터 요청 시 네트워크 요청을 최소화하고, 자동으로 최신 상태를 유지.

차이점 2: 로딩 상태 관리

• 기존 방식: 페이지 새로 고침 시마다 로딩 상태가 반복적으로 표시.
• React Query: 이미 캐시된 데이터가 있으면 로딩 없이 바로 데이터를 표시하고, 필요 시 백그라운드에서 갱신.

React Query의 캐싱과 탭 전환

여기서 중요한 점은 다른 탭으로 넘어갔다가 다시 돌아올 때도 React Query는 이미 캐시된 데이터를 사용한다는 점입니다. 기존 방식에서는 페이지를 새로고침할 때마다 데이터를 요청해야 했지만, React Query는 데이터가 캐시되기 때문에 같은 데이터를 다시 요청하지 않고, 캐시된 데이터를 사용합니다.

예를 들어, useQuery로 한 번 데이터를 요청하고 나면, 해당 데이터는 캐시에 저장됩니다. 다른 페이지나 탭으로 이동하더라도, 같은 데이터를 다시 요청하지 않고 캐시된 데이터를 즉시 사용할 수 있습니다.

만약 데이터가 변경되었다면, React Query는 invalidateQueriesrefetchQueries를 통해 데이터를 새로 요청하여 자동으로 최신 데이터를 반영합니다.

이렇게 React Query는 데이터 요청을 효율적으로 관리하며, 불필요한 요청을 줄이고, 사용자 경험을 개선할 수 있습니다.

🚨문제: 데이터 변경 요청 후 캐시된 데이터 유지

문제는, userData 수정 요청을 보내고 데이터를 변경한 후에도 캐시된 데이터가 계속 유지된다는 점입니다. 예를 들어, 사용자가 자신의 정보를 수정하는 API 요청을 보낸 후, 데이터베이스에는 수정된 내용이 반영되었지만, React Query의 캐시에는 여전히 이전 데이터가 남아 있을 수 있습니다. 이 경우, 수정된 데이터가 즉시 반영되지 않고, 여전히 오래된 캐시 데이터를 사용하게 됩니다.

문제 발생 이유

• 캐시와 서버 데이터 간 불일치: useQuery로 데이터를 요청한 후 React Query는 캐시된 데이터를 사용하고, 수정된 데이터는 서버에서 갱신되었지만, 캐시에는 여전히 이전 데이터가 저장되어 있을 수 있습니다.

• 자동 갱신 미흡: invalidateQueries나 refetchQueries를 사용하지 않으면, 수정된 데이터를 즉시 다시 요청하지 않기 때문에 캐시된 데이터가 계속 사용됩니다.

refetch( ) 사용으로 해결하기

이 문제를 해결하기 위해서는 useQuery로 데이터를 요청하고, refetch( )를 사용하여 수정된 데이터를 강제로 다시 가져오는 방법이 필요합니다.

refetch( )는 useQuery로 가져온 데이터를 다시 요청하는 함수로, 데이터 수정 후 필요한 시점에만 다시 요청을 보내어 최신 상태로 동기화할 수 있습니다.

예를 들어, userData를 수정한 후 refetch( )를 사용하여 해당 데이터를 다시 가져올 수 있었습니다:

  const { refetch } = useFetchUser();
  const handleChangeComplete = (color: ColorResult) => {
    setState(color.hex);
  };

  const onClickEditBtn = async () => {
    const result = await editColor({ color: state });

    if (result.result) {
      alert("색상이 변경되었습니다.");
      refetch();
      return;
    }

    alert("오류가 발생했습니다.");

    return;
  };

결론

React Query는 서버 부하를 줄이고, 데이터 요청을 효율적으로 관리하는 데 중요한 도구입니다. 자동 캐싱과 데이터 갱신 기능을 통해 불필요한 네트워크 요청을 줄이고, 서버에 가는 요청을 최소화할 수 있습니다. 특히 refetch( )를 사용하여 데이터 수정 후 즉시 최신 데이터를 요청할 수 있어, 서버와 클라이언트 간의 데이터 동기화를 원활하게 할 수 있습니다.

기존 방식에서 발생할 수 있는 중복된 요청과 성능 문제를 해결하면서, React Query는 데이터 관리의 효율성을 높이고 서버 성능 최적화에도 기여할 수 있습니다.

따라서, React Query는 단순한 데이터 요청 라이브러리를 넘어서, 서버 자원을 효율적으로 관리하고 성능을 최적화하는 데 필요한 필수 도구라고 할 수 있습니다.

프론트엔드 카테고리와 관련된 최신 글

피크민 블룸은 어떻게 꽃길🌸을 남길까?



Next.js 마이그레이션: 서버 컴포넌트(SSC)와 클라이언트 컴포넌트(CSC) 선택하기

최고의 프로젝트 구조를 찾아서....

React Query로 데이터 처리의 효율성 높이기

스파게티 코드 갱생시키기 프로젝트