Tanstack Query 기초(1)

우혁·2024년 5월 5일

Tanstack Query

목록 보기
1/3

React Query

리액트에서 서버 데이터 가져오기, 캐싱, 동기화 및 업데이트를 쉽게 다룰 수 있도록 도와주는 라이브러리이다.

기존 상태 관리 라이브러리인 redux, recoil 등은 클라이언트 상태 작업에 적합하지만, 비동기 또는 서버 상태 작업에는 그다지 좋지 않다고 한다.

💡 React Query v4부터는 TanStack Query로 라이브러리 이름이 변경되었다.


useQuery, 데이터 가져오기

  • useQuery는 v5부터 인자로 단 하나의 객체만 받는다. 그 중 첫 번째 인자가 queryKey, queryFn가 필수 값이다.
import { useQuery } from '@tanstack/react-query';

// queryFn의 반환 타입을 지정해주면 useQuery의 타입 추론이 원활해진다.
const getPosts = async(): Promise<AxiosResponsePost[]>> {
  return await axios.get(`${BASE_URL}/posts`);
}


function HomePage() {
  const {data, isLoading} = useQuery({
    queryKey: ['posts'], // 필수 값
    queryFn: getPosts // 필수 값
    // ...options ex) geTime, staleTime, select 등등...
  });

  return <div>홈페이지</div>;
}

export default HomePage;

1. queryKey

  • 배열로 지정해줘야 한다. 단일 문자열만 포함된 배열이 될 수도 있고, 여러 문자열과 중첩된 객체로 구성된 복잡한 형태일 수도 있다.
  • 데이터를 고유하게 식별하기 위해 쿼리 함수에 아래와 같이 편리하게 전달할 수 있다.
const getPosts = async ({
  queryKey
}: { 
  queryKey: ["posts", number]
}): Promise<AxiosResponse<post>> => {
  const postId = queryKey[1]; // ex) queryKey: ["posts", "3"]

  return await axios.get(`${BASE_URL}/posts`);
};

const usePostData = (number: string) => {
  return useQuery({
    queryKey: ["posts", postId],
    queryFn: getPosts,
  });
};

💫 useQuery는 queryKey를 기반으로 쿼리 캐싱을 관리하는 것이 핵심이다.

  • 만약 쿼리가 특정 변수에 의존한다면 배열에다가 이어서 줘야 한다.
    ex) ["posts, postId, ..."]
  • queryClient.setQueryData 등과 같이 특정 쿼리에 접근이 필요할 때 초기에 설정해둔 포맷을 지켜줘야 제대로 쿼리에 접근할 수 있다.
  • usePostDataqueryKey["posts", postId]이다. queryClient.setQueryData를 이용할 때 똑같이 ["posts", postId] 포맷을 가져야 한다. 그렇지 않으면 원하는 쿼리에 접근할 수 없다.

2. queryFn

  • useQueryqueryFnPromise를 반환하는 함수를 넣어야 한다.
// 위에 queryKey 예제와 반대로 queryFn 자체적으로 인자를 받는 형태
const getPosts = async (postId: string): Promise<AxiosResponse<Post>> => {
	return await axios.get(`${BASE_URL}/posts`);
};

const usePostData = (postId: string) => {
  return useQuery({
    queryKey: ["posts", postId],
    queryFn: () => getPosts(postId), 
  });
};

useQuery의 주요 리턴 데이터

  • data: 쿼리 함수가 리턴한 Promise에서 resolved된 데이터

  • error: 쿼리 함수에 오류가 발생한 경우, 쿼리에 대한 오류 객체

  • status: 쿼리 결과 값에 대한 상태를 표현하는 status는 문자열 형태로 3가지의 값이 존재한다.

    • pending: 쿼리 데이터가 없고, 쿼리 시도가 아직 완료되지 않는 상태.
      • { enabled: false } 상태로 쿼리가 호출되면 이 상태로 시작한다.
      • error: 에러가 발생했을 때 상태
    • success: 쿼리 함수가 오류없이 요청 성공하고 데이터를 표시할 준비가 된 상태
  • fetchStatus: queryFn에 대한 정보를 나타냄

    • fetching: 쿼리가 현재 실행 중인 상태
    • paused: 쿼리를 요청했지만, 잠시 중단된 상태(netword mode와 연관)
    • idle: 쿼리가 현재 아무 작업도 수행하지 않는 상태
  • isLoading: 캐싱 된 데이터가 없을 때 즉 처음 실행된 쿼리일 때 로딩 여부에 따라 true/false로 반환된다.

    • 캐싱 된 데이터가 있다면 로딩 여부에 상관 없이 false를 반환한다.
    • isFetching && isPending과 동일하다.
  • isFetching: 캐싱 된 데이터가 있더라도 쿼리가 실행되면 로딩 여부에 따라 true/false로 반환된다.

  • isSuccess: 쿼리 요청에 성공하면 true를 반환한다.

  • isError: 쿼리 요청 중에 에러가 발생한 경우 true를 반환한다.

  • refetch: 쿼리를 수동으로 다시 가져오는 함수이다.

그 외 반환 데이터들을 자세히 알고 싶다면 useQuery 공식 문서를 참고하면 된다.

⭐️ status, fetchStatus 나눠서 다루는 이유는 뭘까?

fetchStatus는 HTTP 네트워크 연결 상태아 좀 더 관련된 상태 데이터이다.

  • status가 success 상태라면 주로 fetchStatus는 idle 상태지만, 백그라운드에서 refetch가 발생할 때 fetching 상태 일 수 있다.

  • status가 보통 loading 상태일 때 fetchStatus는 주로 fetching 상태를 갖지만, 네트워크 연결이 안되어 있는 경우 paused 상태를 가질 수 있다.

📝 정리하기

  • status는 data가 있는지 없는지에 대한 상태를 의미한다.
  • fetchStatus는 queryFn 요청이 진행 중인지 아닌지에 대한 상태를 의미한다.

useQuery의 주요 옵션

1. StaleTime과 gcTime

  • stale은 썩은이라는 의미이다. 즉 최신 상태가 아니라는 의미이다.
  • fresh는 신선한이라는 의미이다. 즉 최신 상태를 의미한다.
const {
  data,
  // ...
} = useQuery({
  queryKey: ["posts"],
  queryFn: getAllPosts,
  gcTime: 5 * 60 * 1000, // 5분
  staleTime: 1 * 60 * 1000, // 1분
});
  • staleTime: (number | Infinity)

    • 데이터가 fresh에서 stale 상태로 변경되는 데 걸리는 시간이다. 만약 staleTime이 3000이라면 fresh 상태에서 3초 뒤에 stale로 변환된다.
    • fresh 상태일 때는 쿼리 인스턴스가 새롭게 mount 되어도 네트워크 요청(fetch)가 일어나지 않는다.
    • staleTime의 기본 값은 0이기 때문에 일반적으로 fetch 후에 바로 stale 상태가 된다.
  • gcTime: (number | Infinity)

    • 데이터가 사용되지 않거나, inactive 상태일 때 캐싱 된 상태로 남아있는 시간이다.
    • 쿼리 인스턴스가 unmount 되면 데이터는 inactive 상태로 변경되며, 캐시는 gcTime만큼 유지된다.
    • gcTime이 지나면 가비지 콜렉터로 수집된다.
    • gcTime이 지나기 전에 쿼리 인스턴스가 다시 mount 되면, 데이터를 fetch 하는 동안 캐시 데이터를 보여준다.
    • gcTime은 staleTime과 관계없이, 무조건 inactive 된 시점을 기준으로 캐시 데이터 삭제를 결정한다.
    • gcTime의 기본값은 5분이다. SSR 환경에서는 Infinity이다.

🚨 주의 사항

  • staleTime과 gcTime의 기본 값은 각각 0분, 5분이다. 따라서 staleTime에 어떠한 설정도 하지 않으면 해당 쿼리를 사용하는 컴포넌트가 mount 됐을 때 매번 다시 API를 요청할 것이다.

  • staleTime을 gcTime보다 길게 설정하면, staleTime만큼의 캐싱을 기대했을 때 원하는 결과를 얻지 못할 것이다. 두 개의 옵션을 적절하게 설정해주는 것이 중요하다.

📝 정리하기

  • staleTime이 지나기전에 쿼리 인스턴스가 새롭게 mount되어도 API 요청을 보내지 않는다.

  • 컴포넌트가 unmount 되면 쿼리가 inactive 상태가 되고 gcTime이 지나기 전에 다시 mount가 되는 경우에 API 요청을 보내지 않는다.

  • staleTime과 gcTime이 모두 지나고 데이터를 다시 fetch하는 경우에는 사실상 처음 데이터를 가져오는 것과 동일한 과정을 거친다.


2. refetchOnMount

  • 데이터가 stale 상태일 경우, mount마다 refetch를 실행하는 옵션이다.

  • 기본값은 true이다.

  • always로 설정하면 마운트 시마다 매번 refetch를 실행한다.

  • false로 설정하면 최초 fetch 이후에는 refetch 하지 않는다


3. refetchOnWindowFocus

  • 데이터가 stale 상태일 경우 윈도우 포커싱 될 때마다 refetch를 실행하는 옵션이다.

  • 기본값은 true이다.
    크롬에서 다른 탭을 눌렀다가 다시 원래 보던 중인 탭을 눌렀을 때도 이 경우에 해당한다. 심지어 F12로 개발자 도구 창을 켜서 네트워크 탭이든, 콘솔 탭이든 개발자 도구 창에서 페이지 내부를 다시 클릭했을 때도 이 경우에 해당한다.

  • always로 설정하면 항상 윈도우 포커싱 될 때마다 refetch를 실행한다는 의미이다.


4. Polling

Polling(폴링)이란?
실시간 웹을 위한 기법으로 일정한 주기(특정한 시간)를 가지고 서버와 응답을 주고받는 방식이 폴링 방식이다.
Reqct Query에서는 refetchInterval, refetchIntervalInBackground을 이용해서 구현할 수 있다.

  • refetchInterval: 시간(ms)를 값으로 넣어주면 일정 시간마다 자동으로 refetch를 시켜준다.
  • refetchIntervalInBackground: refetchInterval과 함께 사용하는 옵션이다.
    탭/창이 백그라운드에 있는 동안 refetch 시켜준다. 즉, 브라우저에 focus 되어 있지 않아도 refetch를 시켜주는 것을 의미한다.

5. enabled refetch

  • 쿼리가 자동으로 실행되지 않도록 할 때 설정할 수 있다.

  • enabled를 false를 주면 쿼리가 자동 실행되지 않는다.

    • useQuery 반환 값 중 status가 pending 상태로 시작한다.
  • refetch는 쿼리를 수동으로 다시 요청하는 기능이다. 쿼리 오류가 발생하면 오류만 기록된다.

    • 오류를 발생시키려면 throwOnError 속성을 true로 해서 전달해야 한다.
  • 보통 자동으로 쿼리 요청을 하지 않고 버튼 클릭이나 특정 이벤트를 통해 요청을 시도할 때 같이 사용한다.

  • 주의할 점은, enabled: false를 줬다면 queryClient가 쿼리를 다시 가져오는 방법 중 invalidateQueriesrefetchQueries를 무시한다.


6. retry

  • 쿼리가 실패하면 useQuery를 특정 횟수만큼 재요청하는 옵션이다.

  • retry가 false인 경우, 실패한 쿼리는 기본적으로 다시 시도하지 않는다. true인 경우에는 실패한 쿼리에 대해서 무한 재요청을 시도한다.

  • 값으로 숫자를 넣을 경우, 실패한 쿼리가 해당 숫자를 충족할 때까지 요청을 재시도한다.

  • 기본 값은 클라이언트 환경에서는 3, 서버 환경에서는 0이다.


7. placeholderData

  • placeholderData를 설정하면 쿼리가 pending 상태인 동안 특정 쿼리에 대한 placeholder data로 사용된다.

  • placeholderData는 캐시에 유지되지 않으며, 서버 데이터와 관계없는 보여주기용 가짜 데이터다.

  • placeholderData에 함수를 제공하는 경우 첫 번째 인자로 이전에 관찰된 쿼리 데이터를 수신하고, 두 번째 인자는 이전 쿼리 인스턴스가 된다.

v4까지 있던 keepPreviousData은 페이지네이션과 같은 기능을 구현할 때 많이 사용하던 옵션이었다. 캐싱 되지 않은 페이지를 가져올 때 목록이 깜빡거리는 현상을 방지할 수 있다.

이들은 각각 placeholderDataisPlaceholderData 플래그와 거의 유사하게 동작하여 v5부터 keepPreviousData, isPreviousData은 옵션은 제거됐다.

아래 예시처럼 작성해서 위의 keepPreviousData와 동일한 동작을 할 수 있다.

const {
  data,
  // ...
} = useQuery({
  queryKey: ["posts"],
  queryFn: getAllPosts,
  placeholderData: (previousData, previousQuery) => previousData,
});

profile
🏁

0개의 댓글