본문 바로가기

공부/상태 관리

TanStack Query 알아보기 2 (동작 원리)

반응형

 

SWR 전략 📖

사전

stale을 사전에 검색해 보면 `신선하지 않은``오래된`이라는 뜻을 가지고 있습니다. 

말 그대로 `SWR(stale while revalidate)`은 최신 데이터가 도착하기 전까지 기존 캐시 데이터를 사용하는 전략입니다.

 

그런데 이`캐시 데이터`어디에 보관되는 걸까요

캐시 데이터 📖

TanStack Query는 `캐시 데이터`를 전역적으로 관리합니다. `QueryClientProvider`를 사용해 React 애플리케이션 전체에서 캐시 데이터에 접근할 수 있습니다.

 

이렇게 될 수 있는 이유는 TanStack Query는 내부적으로 `React Context API`를 사용해 구현되어 있어서 모든 자식 컴포넌트가 캐시 데이터에 접근할 수 있기 때문입니다.

 

데이터 흐름

 

주요 흐름

 

<A 컴포넌트>

1. A 컴포넌트에서 `useQuery`가 실행되면서 `todos`라는 `Query Key`를 기준으로 캐시 컨텍스트에 데이터를 요청합니다.

2. 최초 상태에는 todos라는 Query Key가 없기 때문에 data는 `undefined`가 됩니다.

3. 이후 `getTodos`(query function)을 호출하고, 실행이 완료되면 외부에서 가져온 todos데이터를 캐시 컨텍스트에 담습니다.

4. A 컴포넌트에 값을 반영시키기 전에 re-rendering이 발생합니다.

 

왜 리렌더링이 발생할까요?

  • React 컴포넌트는 state나 props가 변경되면 다시 렌더링 하듯이 useQuery hook 역시 데이터 패칭 상태가 변경될 때 컴포넌트를 다시 렌더링 합니다.

5. useQuery가 다시 한번 실행되며, todos라는 Query key를 기준으로 캐시 컨텍스트에 데이터를 요청합니다.

6. 캐싱 데이터가 존재하기 때문에 data를 반환해 줍니다.

 

<B 컴포넌트>

1. B 컴포넌트에서 `useQuery`가 실행되면서 `todos`라는 `Query Key`를 기준으로 캐시 컨텍스트에 데이터를 요청합니다.

2. 캐시 데이터가 존재하기 때문에 data를 반환해 줍니다.

 

<C 컴포넌트>

1. C 컴포넌트는 어떤 액션에 의해 `addTodo` API 호출이 되면 데이터를 갱신합니다.

2. 성공적으로 완료가 되면 자동으로 캐시 데이터가 갱신되지 않기 때문에 `Query Key`기준으로 `invalidateQueries`처리를 해 오래된 데이터를 새것으로 갈아 끼웁니다.

3. `invalidateQueries`처리를 하게 되면 `useQuery`를 이용해 캐시 데이터를 활용했던 모든 곳에서 fresh 한 데이터를 구독하게 됩니다.

 

예제 코드

todos라는 이름의 캐시 데이터가 존재한다고 가정해 보고 아래 예제 코드를 출력하게 되면 어떤 결과가 나올까요?

import { useQuery } from "@tanstack/react-query";
import { getTodos } from "../api/todoList";

export default function Empty() {
  const { data, isFetching } = useQuery({
    queryKey: ["todos"],
    queryFn: getTodos,
  });
  console.log("빈페이지에서 todos: ", data);
  // console.log("isFetching: ", isFetching);
  return <h1>빈 페이지입니다.</h1>;
}

// src > api > todoList.js
export const getTodos = async ({ signal }) => {
  console.log("getTodos 호출");
  const response = await axios.get("http://localhost:5000/todos", { signal });
  return response.data;
};

로그

 

결과 흐름 

1. todos라는 `querykey`로 캐시 데이터가 존재하기 때문에 data 출력합니다.

2. getTodos 호출합니다.

3. 캐시 데이터는 존재하지만 아무런 설정을 하지 않았기 때문에 캐시 데이터에 저장한 값은 가져오자마자 오래된(stale) 데이터로 취급합니다. 

4. 다시 api 호출해 캐시 데이터를 갱신하면서, 기존에 존재했던 오래된 값을 새로운 값으로 대체합니다. 이 과정에서 데이터가 변경됐기 때문에 리렌더링이 발생하면서 useQuery는 다시 실행 돼 `todos`라는 queryKey로 신선한 데이터를 가져옵니다.

 

이 과정에서 SWR 전략이 사용됩니다.

 

LifeCycle 📖

TanStack Query의 생명주기

TanStack Query의 생명주기는 데이터가 캐시 되고, 사용되고, 갱신되는 과정을 포함합니다.

lifecycle

 

주요 상태

상태 설명
fresh 데이터를 새로 패칭할 필요가 없는 상태
stale 데이터를 새로 패칭해야 하는 상태
active 현재 컴포넌트에서 사용 중인 쿼리 상태, 컴포넌트가 마운트되어 쿼리를 사용하고 있을 때
inactive 더 이상 사용되지 않는 쿼리 상태
deleted 캐시에서 제거된 쿼리 상태
fetching 데이터를 서버에서 가져오고 있는 상태

 

기본 설정

기본 설정 의미
staleTime: 0 useQuery 또는 useInfiniteQuery에 등록된 queryFn을 통해 fetch 받아온 데이터는 항상 stale data 취급
refetchOnMount: true useQuery 또는 useInfiniteQuery가 있는 컴포넌트가 마운트 시 stale data를 refetch 자동 실행
refetchOnWindowFocus: true 실행중인 브라우저 화면을 focus 할 때 마다 stale data를 refetch 자동 실행
refetchOnReconnect: true Network 가 끊겼다가 재연결 되었을 때 stale data를 refetch 자동 실행
gcTime(cacheTime): 5분 (1000 * 60 * 5 ms) useQuery 또는 useInfiniteQuery가 있는 컴포넌트가 언마운트 되었을 때 inactive query라 부르며, inactive 상태가 5분 경과 후 GC(가비지콜렉터)에 의해 cache data 삭제 처리
retry: 3 useQuery 또는 useInfiniteQuery에 등록된 queryFn 이 API 서버에 요청을 보내서 실패하더라도 바로 에러를 띄우지 않고 총 3번까지 재요청을 자동으로 시도

 

예제 코드

QueryClient 기본 옵션으로 `staleTime`을 5초로 설정했습니다.

// src > main.jsx
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5000,
    },
  },
});

ReactDOM.createRoot(document.getElementById("root")).render(
  <QueryClientProvider client={queryClient}>
    <ReactQueryDevtools initialIsOpen={false} />
    <App />
  </QueryClientProvider>
);

 

fresh 한 상태가 설정한 시간만큼 유지되는 것을 확인할 수 있습니다.

 

개념 정리 📖

staleTime vs gcTime

`staleTime`: 얼마의 시간이 흐른 뒤 stale 취급할 건지 (default: 0)

`gcTime`: inactive 된 이후 메모리에 얼마만큼 있을 건지 (default: 5분, gcTime 0 되면 삭제)

staleTime과 stale/fresh 관계

`staleTime > 0`: fresh data

`staleTime = 0`: stale data

isPending vs isFetching

`isPending`: 새로운 캐시 데이터를 서버에서 받고 있는지 여부

  • 캐시 데이터가 있는 경우 isPending은 false, isFetching은 true

`isFetching`: 서버에서 데이터를 받고 있는 지 여부

 

알아야 할 옵션 📖

 enabled

`enabled`옵션은 queryFn 실행 여부를 제어합니다. 기본값은 `true`이며, `false`로 설정하면 쿼리가 자동으로 실행되지 않습니다.

// 이벤트 발생 시에만 수동으로 실행

const { data, refetch } = useQuery({
  queryKey: ["todos"],
  queryFn: getTodos,
  enabled: false
});

return (
  <div>
    <button onClick={() => refetch()}>데이터 불러오기</button>
  </div>
);

 

select

`select`옵션은 쿼리 함수에서 데이터를 변형해 사용할 수 있습니다.

import { useQuery } from 'react-query'

function User() {
  const { data } = useQuery({
    queryKey: ["user"],
    queryFn: fetchUser,
    select: user => user.username
  });
  
  return <div>Username: {data}</div>
}

 

 

 

이전 게시물

 

TanStack Query 알아보기 1 (기본 사용법)

TanStack Query 📖서버 상태를 관리하기 위한 라이브러리로 서버 상태를 페칭, 캐싱, 동기화, 업데이트하는 작업을 아주 쉽게 해 줍니다.주요 기능`데이터 캐싱`: 동일한 데이터를 여러 번 요청하지

mingos-habitat.tistory.com

다음 게시물

 

TanStack Query 알아보기 3 (Query Cancellation, Optimistic Updates, Prefetching, Paginated, Infinite Queries)

Query Cancellation 📖불필요한 네트워크 요청을 제거하기 위해 사용합니다. 대용량 fetching을 중간에 취소하거나 사용하지 않는 컴포넌트에서 fetching이 진행 중이면 자동으로 취소시켜 불필요한 네

mingos-habitat.tistory.com

 

반응형