Docs
PLUGINS
persistQueryClient

persistQueryClient

이 유틸리티 세트는 "persisters"와 상호작용하여 queryClient를 나중에 사용할 수 있도록 저장하는 데 도움을 줍니다. 다양한 persisters를 사용하여 클라이언트와 캐시를 여러 저장소 계층에 저장할 수 있습니다.

Build Persisters

How It Works

중요 - persistence가 제대로 작동하려면 QueryClientgcTime 값을 설정하여 hydration 중 기본값을 덮어쓰는 것이 좋습니다 (위 예제 참조).

QueryClient 인스턴스를 생성할 때 gcTime이 설정되지 않으면 기본값인 300000 (5분)으로 설정되며, 5분 동안 비활성 상태가 되면 저장된 캐시가 삭제됩니다. 이것이 기본적인 garbage collection 동작입니다.

이 값은 persistQueryClientmaxAge 옵션과 동일하거나 더 높은 값으로 설정해야 합니다. 예를 들어 maxAge가 24시간(기본값)인 경우 gcTime도 24시간 이상으로 설정해야 합니다. maxAge보다 낮으면 garbage collection이 작동하여 예상보다 일찍 저장된 캐시를 삭제할 수 있습니다.

gcTimeInfinity로 설정하여 garbage collection 동작을 완전히 비활성화할 수도 있습니다.

JavaScript의 제한으로 인해 최대 허용 gcTime은 약 24일입니다 (자세한 내용은 여기 (opens in a new tab)를 참조하세요).

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      gcTime: 1000 * 60 * 60 * 24, // 24시간
    },
  },
});

Cache Busting

어플리케이션이나 데이터에 대한 변경으로 인해 즉시 모든 캐시된 데이터가 무효화되어야 하는 경우가 있습니다. 이럴 때는 buster 문자열 옵션을 사용할 수 있습니다. 캐시에서 이 buster 문자열이 포함되지 않은 경우, 캐시는 삭제됩니다. 다음 여러 함수에서 이 옵션을 사용할 수 있습니다:

persistQueryClient({ queryClient, persister, buster: buildHash });
persistQueryClientSave({ queryClient, persister, buster: buildHash });
persistQueryClientRestore({ queryClient, persister, buster: buildHash });

Removal

데이터가 다음 중 하나로 판명된 경우:

  1. 만료된 경우 (see maxAge)
  2. 무효화된 경우 (see buster)
  3. 오류가 발생한 경우 (예: throws ...)
  4. 비어 있는 경우 (예: undefined)

removeClient()가 호출되고 캐시가 즉시 삭제됩니다.

API

persistQueryClientSave

  • 쿼리/변경 사항이 dehydrated되고 제공한 persister에 의해 저장됩니다.
  • createSyncStoragePersistercreateAsyncStoragePersister는 이 작업이 최대 1초마다 한 번만 수행되도록 조절하여 잠재적으로 비용이 많이 드는 쓰기를 줄입니다. 조정 가능한 throttle timing에 대한 문서를 검토하십시오.

원하는 순간에 캐시를 명시적으로 저장하는 데 사용할 수 있습니다.

persistQueryClientSave({
  queryClient,
  persister,
  buster = "",
  dehydrateOptions = undefined,
});

persistQueryClientSubscribe

persistQueryClientSave를 사용하여 queryClient의 캐시가 변경될 때마다 자동으로 저장합니다. 예를 들어, 사용자가 로그인할 때 "Remember me"를 체크하면 subscribe를 시작할 수 있습니다.

  • unsubscribe 함수를 반환하며, 이를 통해 모니터링을 중단하고 persisted cache 업데이트를 종료할 수 있습니다.
  • unsubscribe 이후에 persisted cache를 지우고 싶다면, 새로운 busterpersistQueryClientRestore에 전달하여 persister의 removeClient 함수를 호출하고 persisted cache를 삭제할 수 있습니다.
persistQueryClientSubscribe({
  queryClient,
  persister,
  buster = "",
  dehydrateOptions = undefined,
});

persistQueryClientRestore

  • persister에서 이전에 저장된 dehydrated query/mutation 캐시를 queryClient의 쿼리 캐시로 hydrate하려고 시도합니다.
  • maxAge(기본값: 24시간)보다 오래된 캐시는 삭제됩니다. 이 타이밍은 필요에 맞게 사용자 정의할 수 있습니다.

원하는 순간에 캐시를 복원할 때 사용할 수 있습니다.

persistQueryClientRestore({
  queryClient,
  persister,
  maxAge = 1000 * 60 * 60 * 24, // 24시간
  buster = "",
  hydrateOptions = undefined,
});

persistQueryClient

다음과 같은 작업을 수행합니다:

  1. 즉시 저장된 캐시를 복원합니다 (persistQueryClientRestore 참조).
  2. 쿼리 캐시에 구독하고 unsubscribe 함수를 반환합니다 (persistQueryClientSubscribe 참조).

이 기능은 버전 3.x에서 유지됩니다.

persistQueryClient({
  queryClient,
  persister,
  maxAge = 1000 * 60 * 60 * 24, // 24시간
  buster = "",
  hydrateOptions = undefined,
  dehydrateOptions = undefined,
});

Options

사용 가능한 모든 옵션은 다음과 같습니다:

interface PersistQueryClientOptions {
  /** 저장할 QueryClient */
  queryClient: QueryClient;
  /** 캐시를 저장하고 복원하기 위한 Persister 인터페이스 */
  persister: Persister;
  /** 캐시의 최대 허용 연령(밀리초) */
  maxAge?: number;
  /** 동일한 buster 문자열을 공유하지 않는 경우 캐시를 강제로 무효화할 수 있는 고유한 문자열 */
  buster?: string;
  /** hydrate 함수에 전달되는 옵션
   * `persistQueryClientSave` 또는 `persistQueryClientSubscribe`에서는 사용되지 않음 */
  hydrateOptions?: HydrateOptions;
  /** dehydrate 함수에 전달되는 옵션
   * `persistQueryClientRestore`에서는 사용되지 않음 */
  dehydrateOptions?: DehydrateOptions;
}

실제로 사용할 수 있는 세 가지 인터페이스가 있습니다:

  • PersistedQueryClientSaveOptions: persistQueryClientSavepersistQueryClientSubscribe에서 사용되며, hydrateOptions는 사용되지 않음.
  • PersistedQueryClientRestoreOptions: persistQueryClientRestore에서 사용되며, dehydrateOptions는 사용되지 않음.
  • PersistQueryClientOptions: persistQueryClient에서 사용됩니다.

React와 함께 사용하는 방법

persistQueryClient는 캐시를 복원하고 변경 사항에 자동으로 구독하여 클라이언트를 제공된 저장소와 동기화합니다.

그러나 복원은 비동기적으로 진행되기 때문에, 복원하는 동안 앱을 렌더링하면 쿼리가 동시에 마운트되고 데이터를 가져오는 경우 경쟁 조건이 발생할 수 있습니다.

또한, React 컴포넌트 라이프사이클 외부에서 변경 사항에 구독하는 경우, 구독을 해제할 방법이 없습니다:

// 🚨 동기화에서 구독을 해제하지 않음
persistQueryClient({
  queryClient,
  persister: localStoragePersister,
});
 
// 🚨 복원과 동시에 발생
ReactDOM.createRoot(rootElement).render(<App />);

PersistQueryClientProvider

이 경우에는 PersistQueryClientProvider를 사용할 수 있습니다. 이 컴포넌트는 React 컴포넌트 라이프사이클에 따라 구독 및 구독 해제를 올바르게 처리하며, 복원 중에 쿼리가 데이터를 가져오는 것을 방지합니다. 쿼리는 여전히 렌더링되지만, 데이터가 복원될 때까지 fetchingState: 'idle' 상태로 유지됩니다. 데이터가 복원되면, 데이터가 충분히 fresh하면 쿼리가 다시 가져오지 않고, initialData도 존중됩니다. 이는 일반 QueryClientProvider 대신 사용할 수 있습니다:

import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client";
import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister";
 
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      gcTime: 1000 * 60 * 60 * 24, // 24시간
    },
  },
});
 
const persister = createSyncStoragePersister({
  storage: window.localStorage,
});
 
ReactDOM.createRoot(rootElement).render(
  <PersistQueryClientProvider
    client={queryClient}
    persistOptions={{ persister }}
  >
    <App />
  </PersistQueryClientProvider>
);

Props

PersistQueryClientProviderQueryClientProvider와 동일한 props를 사용하며, 추가로:

  • persistOptions: PersistQueryClientOptions
  • onSuccess?: () => Promise<unknown> | unknown
    • 선택 사항
    • 초기 복원이 완료되면 호출됨
    • resumePausedMutations를 호출하는 데 사용될 수 있음
    • Promise가 반환되면, 해당 Promise가 해결될 때까지 복원은 진행 중으로 간주됨

useIsRestoring

PersistQueryClientProvider를 사용할 때, useIsRestoring 훅을 사용하여 현재 복원 작업이 진행 중인지 확인할 수 있습니다. 이 훅은 복원 과정과 쿼리의 마운트 사이에서 발생할 수 있는 경쟁 조건을 방지하는 데 유용할 수 있습니다.

다음은 useIsRestoring을 사용하는 예입니다:

import { useIsRestoring } from "@tanstack/react-query-persist-client";
 
function MyComponent() {
  const isRestoring = useIsRestoring();
 
  if (isRestoring) {
    return <div>데이터 복원 중...</div>;
  }
 
  return <div>데이터 준비 완료!</div>;
}

이렇게 하면 컴포넌트가 복원이 완료될 때까지 로딩 상태를 표시하거나 작업을 방지할 수 있습니다.

Persisters

Persisters Interface

Persisters는 캐시된 데이터를 저장, 복원 및 제거하는 인터페이스를 제공합니다. 인터페이스는 다음과 같습니다:

export interface Persister {
  persistClient(persistClient: PersistedClient): Promisable<void>;
  restoreClient(): Promisable<PersistedClient | undefined>;
  removeClient(): Promisable<void>;
}

PersistedClient는 다음과 같이 정의됩니다:

export interface PersistedClient {
  timestamp: number;
  buster: string;
  cacheState: any;
}

이 타입들을 가져와서 커스텀 persister를 만들 때 사용할 수 있습니다:

import {
  PersistedClient,
  Persister,
} from "@tanstack/react-query-persist-client";

Building A Persister

다양한 저장소 솔루션을 위해 persisters를 만들 수 있습니다. 예를 들어, IndexedDB (opens in a new tab)를 사용하는 persister를 만드는 방법은 다음과 같습니다. IndexedDB는 Web Storage API보다 더 많은 저장 용량을 지원하며, 직렬화가 필요 없습니다.

IndexedDB persister의 기본 구현 예시는 다음과 같습니다:

import { get, set, del } from "idb-keyval";
import {
  PersistedClient,
  Persister,
} from "@tanstack/react-query-persist-client";
 
/**
 * Indexed DB persister를 생성합니다
 * @see https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
 */
export function createIDBPersister(idbValidKey: IDBValidKey = "reactQuery") {
  return {
    persistClient: async (client: PersistedClient) => {
      await set(idbValidKey, client);
    },
    restoreClient: async () => {
      return await get<PersistedClient>(idbValidKey);
    },
    removeClient: async () => {
      await del(idbValidKey);
    },
  } as Persister;
}

이 예제에서는:

  • persistClientPersistedClient를 IndexedDB에 저장합니다.
  • restoreClient는 IndexedDB에서 PersistedClient를 가져옵니다.
  • removeClient는 IndexedDB에서 PersistedClient를 삭제합니다.

필요에 따라 다른 저장 메커니즘이나 요구 사항에 맞게 이 구현을 사용자화할 수 있습니다.