리액트 쿼리 useQuery 사용법

Jin·2022년 3월 11일
5

React

목록 보기
12/18
post-custom-banner

리액트 쿼리는 fetching, caching, updating 등을 다양한 옵션과 간단한 로직으로 해결할 수 있게 해줍니다.

그 중에서 useQuery는 GET 요청을 보낼 때 사용합니다.

초기 설정은 Context API와 비슷합니다.

import React from 'react'
import styled from 'styled-components'
import { QueryClient, QueryClientProvider } from 'react-query'

function App() {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        refetchOnWindowFocus: false,
        refetchOnMount: false,
      },
    },
  })

  return (
    <QueryClientProvider client={queryClient}>
      <Main>
        ...
      </Main>
    </QueryClientProvider>
  )
}

Context API에서 Provider로 감쌌다면 리액트 쿼리에서는 QueryClientProvider로 감싸야 하고 client라는 prop에 위에서 설정한 queryClient를 넣어주어야 합니다.

queryClient에서는 공통적으로 적용할 설정을 해줄 수 있습니다.

제가 설정한 것은 아래와 같습니다.

  • refetchOnWindowFocus: 윈도우에 포커스가 되면 데이터를 다시 가져오는 것
  • refetchOnMount: DOM에 컴포넌트가 처음 생성되었을 때 데이터를 가져오는 것

다음은 리액트 쿼리의 옵션과 결과값입니다.

const {
   data,
   dataUpdatedAt,
   error,
   errorUpdatedAt,
   failureCount,
   isError,
   isFetched,
   isFetchedAfterMount,
   isFetching,
   isIdle,
   isLoading,
   isLoadingError,
   isPlaceholderData,
   isPreviousData,
   isRefetchError,
   isRefetching,
   isStale,
   isSuccess,
   refetch,
   remove,
   status,
 } = useQuery(queryKey, queryFn?, {
   cacheTime,
   enabled,
   initialData,
   initialDataUpdatedAt
   isDataEqual,
   keepPreviousData,
   meta,
   notifyOnChangeProps,
   notifyOnChangePropsExclusions,
   onError,
   onSettled,
   onSuccess,
   placeholderData,
   queryKeyHashFn,
   refetchInterval,
   refetchIntervalInBackground,
   refetchOnMount,
   refetchOnReconnect,
   refetchOnWindowFocus,
   retry,
   retryOnMount,
   retryDelay,
   select,
   staleTime,
   structuralSharing,
   suspense,
   useErrorBoundary,
 })

정말 많은 옵션과 세분화된 결과값을 받을 수 있습니다.

  • queryKey
    • 하나의 key만 넣을 때는 그냥 넣어도 됩니다. (내부적으로 자동으로 배열 안에 넣게 바꿈)
    • 둘 이상부터는 배열을 선언하여 안에 넣어야 합니다.
    • 이 값을 바탕으로 캐싱을 하게 됩니다.
      • queryKey가 같으면 캐싱 적용 가능
      • queryKey가 다르면 서버에서 데이터 다시 fetch
  • queryFn
    • fetcher가 들어가는 자리입니다.
    • 보통, fetch나 axios를 사용하여 GET 요청을 보내는 함수를 인자로 받습니다.
  • options
    • 선택형 인자로 옵션을 특정할 때에 사용합니다.
      • enable: false시 자동으로 데이터를 불러오는 것을 막습니다. (default: true)
      • retry
        • false: 데이터 fetch에 실패해도 재요청하지 않습니다. (default)
        • true: 데이터 fetch시 실패해도 무한으로 재요청합니다.
        • number: 데이터 fetch에 실패하면 number 번까지만 재요청합니다.
      • staleTime: 캐시가 fresh하다고 인정하는 시간
        • number: number millisecond 후 stale 상태로 처리할 것인지 설정합니다. (default: 0)
        • Infinity: 데이터를 영원히 fresh 상태로 취급합니다.
      • cacheTime: 메모리에 살아있는 시간
        • number: number millisecond 동안 캐시 데이터가 메모리에 남아있게 됩니다. 이 이후 가비지 컬렉션에서 이 데이터를 처리합니다. (default: 5 60 1000 => 5 min)
        • Infinity: 영원히 메모리에 데이터를 보관합니다.
      • onSuccess: (data: TData) => void
        • 데이터 fetch 성공시 실행되는 콜백입니다.
        • 매개변수 data에 요청받은 데이터가 들어옵니다.
      • onError: (error: TError) => void
        • 데이터 fetch 실패시 실행되는 콜백입니다.
        • 매개변수 error에 실패 정보가 담긴 error가 들어옵니다.
      • onSettled: (data?: TData, error?: TError) => void
        • 데이터 fetch 성공, 실패와 관계없이 무조건 동작하는 콜백입니다.
        • 첫번째 인자로 성공했을 때의 데이터, 두번째 인자로 실패했을 때의 에러가 들어옵니다.
      • select: (data: TData) => unknown
        • 데이터를 가공할 때에 쓰이는 함수입니다.
        • 이 함수에서 리턴한 형태가 응답받은 데이터의 형태가 됩니다.
      • keepPreviousData: 새로 fetch한 데이터를 화면에 나오기 전까지 기존에 있던 데이터를 계속 화면에 유지할 지 여부를 설정합니다.
  • 반환값
    • status
      • idle: 초기 상태입니다.
      • loading: 데이터 fetching 중일 때 상태입니다. (isFetching === true)
      • error: 데이터 fetch에 실패한 상태입니다.
      • success: 데이터 fetch에 성공한 상태입니다.
    • isIdle
    • isLoading
    • isFetching
    • isSuccess
    • isError
    • isStale
    • data: 응답받은 데이터
    • error: 실패 정보
    • refetch: 수동으로 데이터 refetch를 실행하는 함수입니다. stale이나 cache같은 설정들이 무시되고 무조건 다시 데이터를 fetching합니다.

다음과 같이 사용할 수 있습니다.

const [searchValue, setSearchValue] = useState<string>('')
const [page, setPage] = useState(1)

const fetcher = (ctx: QueryFunctionContext) => {
  if (ctx.queryKey[1] === '') {
    return { items: [], total_count: -1 }
  }
  
  return get('repositories', { q: `${ctx.queryKey[1]} in:name`, page })
}

async function get(url: string, params: Params) {
  const { data } = await customAxios.get(url, {
    params,
  })
  
  return data
}

  const { data, isFetching } = useQuery([page, searchValue], fetcher, {
    staleTime: 60 * 1000,
    keepPreviousData: true,
  })

useQuery의 첫 번째 인자로 쿼리 키를 넣어주고, 두 번째로는 fetcher를 넣어줍니다.
세 번째에서 옵션을 설정해주고 결과값을 반환 받습니다. 저는 data와 isFetching을 받았습니다.

이제 검색어와 페이지가 달라지면서 상태가 변경되므로 알아서 uri가 변경되어 API 호출을 하게 됩니다.
저는 페이지가 마운트되자마자 데이터가 필요하지 않아서 fetcher 안에서 조건문을 통해서 검색어가 존재할 경우에만 호출을 하도록 하였습니다.

staleTime을 1분으로 두어서 1분 안에 호출했던 쿼리 키로 다시 호출한 경우에는 API를 호출하지 않고 캐시에 있는 데이터를 다시 가져와 사용할 수 있도록 하였습니다.

느낀 점

처음에는 저렇게 fetcher 안에서 분기를 통해 마운트되자마자 API을 호출하지 않는 방법이 아니었습니다.
enabled 옵션을 false로 주고 useEffect로 searchValue와 page가 바뀔 때마다 refetch를 호출하는 방식으로 하였습니다.

그러니까 리액트 쿼리의 핵심인 stale, cache를 전혀 사용하지 않고 무조건 강제로 refetching하게 되어서 위와 같이 개선하였습니다.

리액트 쿼리를 사용하여 여러 옵션들과 캐싱, 유효 시간 등을 설정할 수 있어서 편리하게 비동기 호출을 처리할 수 있었습니다.

특히, 저렇게 {data, isFetching} 처럼 받으면 추가적으로 상태를 선언하지 않아도 되어서 코드가 간결해져서 이전에 있던 코드의 상당 부분을 지워도 되어서 놀랐습니다.

이번에는 useQuery만 써봤는데 다음에는 useMutation을 비롯한 다른 API도 사용해보고 싶은 생각이 들었습니다.

profile
배워서 공유하기
post-custom-banner

0개의 댓글