본문 바로가기
Front-End/React + Native

[React-Query]리액트 쿼리 기본 문법

by MS_developer 2024. 6. 24.

 

리액트 쿼리를 왜 써야 하는지 지난 포스팅에서 언급했으니, 이제 기본 문법들을 정리해 보자.

 

해당 문법들에 대한 설명은 udemy에서 내돈내산한 강의 React Query / TanStack Query: React Server State Management의 내용을 기반으로 하고 있다.


QueryClientProvider

리액트 쿼리를 사용하기 위한 기본 공급자(Provider) 컴포넌트이다.

 

비슷한 예시로 ThemeProvider, RouterProvider를 생각하면 될 것 같다. 상위 컴포넌트에서 한 번 감싸서 사용하면 되고, 보통 queryClient라고 선언하는 인스턴스를 client 속성에 할당해주어야 한다.

 

아래와 같은 형식으로 생성한다.

 

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

const queryClient = new QueryClient()

function App() {
   return (
      <QueryClientProvider client={queryClient}>
      // 여기에 다른 컴포넌트들을 추가
      ...
      </QueryClientProvider>
   );
}

 

이때, 한 가지 의문이 드는 것이 있다.

queryClient는 정확히 어떤 기능일까?

 

 

queryClient는 리액트 쿼리의 캐시에 접근하기 위해서 필요하다.

 

리액트 쿼리는 서버에서 받아온 데이터를 캐싱하고, 캐싱된 데이터를 활용해 비동기 상태를 관리하고 반복적인 API 사용을 줄인다.

 

이러한 기능들을 활용해 데이터를 미리 설정하기 위해서 queryClient가 다리 역할을 해준다고 보면 된다.

 

useQuery()

해당 함수를 통해 본격적으로 캐싱할 데이터를 지정하고 활용할 수 있다.

 

리액트 쿼리는 useQuery 함수를 통해 다양한 설정을 지정할 수 있고, 지정된 설정에 따라 반환되는 값들을 활용해 작업할 수 있다.

 

자세한 내용은 공식 문서를 참고하여 설정하면 된다.

 

이때, queryKeyqueryFn은 반드시 지정해주어야 한다.

 

queryKey캐싱된 또는 캐싱할 데이터를 지정하고 분류하기 위해 필요로 하는 고유 id 값이다. 해당 키를 설정해야 캐싱된 데이터에 접근하여 열람하거나 수정할 수 있다.

 

queryFn은 데이터를 가져오기 위해 실행되는 함수를 정의하기 위한 속성이다. 보통 가져오는 정보는 비동기 함수를 통해 실행하므로, 정의되어 있지 않은 값에 랜더링 할 수 없다. 따라서 이에 따른 에러 핸들링이 필요로 한다.

단, 앞서 선언했던 queryClient에서 미리 queryFn의 함수를 지정할 수 있는데, 이 경우에는 필수는 아니다.

 

아래는 코드를 사용했을 때의 예시이다.

    const {data, isError, isLoading, error} = useQuery(
        {
            queryKey: ["성격에_맞는_쿼리_키", data.id],
            queryFn: () => fetchData(data.id)
        }
    )

 

위 코드에서 대표적으로 가져오는 반환값들을 설정했는데, 이는 활용 여부에 따라 더 많아질 수도 적어질 수도 있다.

 

data는 queryFn을 통해 지정된 함수를 실행하여 성공했을 때 가져오는 비동기 데이터를 말한다.

 

isError는 마찬가지로 queryFn 함수의 실패 여부를 알 수 있는 함수다. 만약 데이터를 가져오지 못했다면 해당 값이 true로 설정되기 때문에 이를 분기로 조건부 랜더링을 할 수 있다.

 

isLoading은 데이터를 가져오는 과정에서 캐싱된 데이터가 없다면 불러오고 있는지 여부를 알 수 있다. 이와 유사한 개념으로 isFetching이 있는데, isFetching은 캐싱된 데이터를 가져오는지 여부도 알 수 있어 좀 더 포괄적인 의미로 생각하면 된다.

 

error는 만약 오류가 발생했을 때 서버 통신을 통해 전달받은 에러 객체를 말한다. 

 

위 반환값들을 활용했을 때 코드는 아래와 같은 방식으로 작성할 수 있다.

 

    if (isLoading) {
        return (
            <div>
                <h3>로딩 중입니다 . . .</h3>
            </div>
        )
    }

    if (isError) {
        return (
            <div>
                <h3>오류가 발생했습니다: </h3>
                <p>{error.toString()}</p>
            </div>
        )
    }
    
return (
   <div>
      데이터를 잘 불러왔네요.
      {data.title}
   </div>
}

 

React Query Dev Tools

리액트 쿼리를 띄엄띄엄 썼을 때 가장 사용하지 않았던 기능인데, tanStack에서 제공하는 디버깅 툴이다.

 

매우매우 유용하다.

 

해당 툴을 통해 개발 단계에서 어떤 쿼리들이 사용되고 활용되고 있는지, 또 해당 쿼리는 현재 어떤 상태인지 알 수 있다.

 

위와 같이 현재 사용 중인 쿼리 키를 기준으로 한 쿼리에 대한 정보들을 볼 수 있게 나열했기 때문에 시각적으로 매우 도움이 된다. 

 

해당 툴은 별도의 설치를 필요로 한다.

 

$ npm i @tanstack/react-query-devtools
# or
$ pnpm add @tanstack/react-query-devtools
# or
$ yarn add @tanstack/react-query-devtools
# or
$ bun add @tanstack/react-query-devtools

 

먼저 라이브러리를 추가적으로 설치한 후

 

import { ReactQueryDevtools } from '@tanstack/react-query-devtools'

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      {/* 나머지 요소들을 여기에 추가 */}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  )
}

 

위와 같이 사용하면 된다.

 

StaleTime vs gcTime

앞서 React Query Dev Tools를 사용했을 때 유용한 점 중 쿼리의 "현재 상태"를 알 수 있다고 했는데, 이는 해당 쿼리를 통해 가져온 데이터의 생애 주기를 알 수 있음을 의미한다.

 

생애 주기(Life Cycle)란, 데이터가 생성되고 삭제될 때까지의 모든 단계를 말한다. 

 

리액트 쿼리는 여기서 데이터 생애 주기를 좀 더 다르게 정의했는데, 기본적인 골자는 다음과 같다.

 

 

1. fresh

말 그대로 데이터가 "신선한" 상태이다. 후술 할 staleTime이 지나지 않은 데이터를 fresh 상태로 분류한다.

 

2. fetching

데이터를 불러오는 중인 상태로, 네트워크 속도를 줄이면 좀 더 쉽게 관찰할 수 있다.

 

3. stale

데이터가 "상한" 상태로 staleTime이 지난 데이터를 의미한다.

 

4. inactive

쿼리가 실행된 컴포넌트가 랜더링되지 않았을 때의 데이터들의 상태이다. 캐싱은 되어 있지만 표시 중이지 않은 데이터들.

 



 

잠시 staleTimegcTime의 개념에 대해 짚고 넘어가자.

 

staleTime은 데이터가 "신선한" 상태(fresh)로 얼마나 보존될지를 결정하는 요소이다. 해당 시간이 지나면 데이터는 "상한" 상태, 즉 stale 상태로 변경된다.

 

staleTime의 기본값은 0초로, 이에 대한 설정을 변경하여 데이터의 생애 주기를 늘릴 수 있다.

 

gcTime에서의 gc는 garbage collection을 의미한다.

 

garbage colleciton이란 생애 주기가 끝났지만 메모리에 남아있는 메모리들에 대해 불필요한 메모리 낭비를 줄이기 위하여 데이터를 삭제하는 과정을 말한다.

 

gcTime이 만료되면 해당 쿼리는 삭제된다. 이때, 데이터가 사용되지 않은 시간을 기점으로 계산한다. 기본값은 5분이다.

 

만약 staleTime 또는 gcTime의 값을 지정하고 싶다면 아래와 같이 코드를 작성하면 된다. 단위를 ms로, 1000ms가 1초에 해당한다.

 

    const {data, isError, isLoading, error} = useQuery(
        {
            queryKey: ["성격에_맞는_쿼리_키", data.id],
            queryFn: () => fetchData(data.id),
            staleTime: 600000,
            gcTime: 900000,
        }
    )

 

useMutation

서버 통신을 통해 데이터를 변경 / 수정 / 삭제할 때 사용하는 내장 함수이다.

 

useQuery와 비슷한 원리로 사용하지만 queryKey를 필요로 하지 않는다.

 

이 말은 즉, 데이터를 캐싱하지 않는다는 뜻이다.

 

캐싱을 하지 않기 때문에 isLoading, isFetching은 없다. 대신 isPending을 통해 통신 여부를 알 수 있다.

 

아래는 예시 코드로, 위 예시들과 같이 구조 분해 할당을 통해 사용하는 편이다.

 

    const {mutate} = useMutation({
        mutationFn: (appointment: Appointment) => setAppointmentUser(appointment, userId),
        onSuccess: () => {
            queryClient.invalidateQueries({queryKey: ["예약_쿼리_키"]});
            toast({title: "예약 성공!", status: 'success'});
        }
    })

 

자세한 내용은 공식문서를 확인하자.

댓글