본문으로 건너뛰기

persistQueryClient

이는 나중에 사용할 수 있도록 queryClient를 저장하는 "persister"와 상호 작용하기 위한 유틸리티 모음입니다. 다양한 persister를 사용하여 client와 캐시를 여러 저장소 계층에 저장할 수 있습니다.

영구 저장기 빌드하기

작동 방식

중요 - 영구 저장이 올바르게 작동하려면, 하이드레이션 중에 기본값을 재정의하도록 QueryClientgcTime 값을 전달하는 것이 좋습니다(위에 표시된 방식과 같습니다).

QueryClient 인스턴스를 생성할 때 설정하지 않으면 하이드레이션 시 기본값은 300000(5분)이며, 저장된 캐시는 5분 동안 비활성 상태가 지속되면 폐기됩니다. 이것이 기본 가비지 컬렉션 동작입니다.

persistQueryClient의 maxAge 옵션과 같거나 더 높은 값으로 설정해야 합니다. 예를 들어 maxAge가 24시간(기본값)이면 gcTime도 24시간 이상이어야 합니다. maxAge보다 낮으면 가비지 컬렉션이 시작되어 저장된 캐시를 예상보다 일찍 폐기합니다.

가비지 컬렉션 동작을 완전히 비활성화하려면 Infinity도 전달할 수 있습니다.

JavaScript의 제한으로 인해 허용되는 최대 gcTime은 약 24일이지만, timeoutManager.setTimeoutProvider를 사용하면 이 제한을 우회할 수 있습니다.

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

캐시 갱신

애플리케이션이나 데이터를 변경하여 캐시된 모든 데이터가 즉시 무효화되는 경우가 있습니다. 이런 일이 발생하면 buster 문자열 옵션을 전달할 수 있습니다. 발견된 캐시에 해당 buster 문자열도 없으면 그 캐시는 폐기됩니다. 다음의 여러 함수가 이 옵션을 허용합니다:

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

제거

데이터가 다음 중 하나로 확인되는 경우:

  1. 만료됨(maxAge 참조)
  2. 무효화됨(buster 참조)
  3. 오류(예: throws ...)
  4. 비어 있음(예: undefined)

persister removeClient()가 호출되고 캐시는 즉시 폐기됩니다.

API

persistQueryClientSave

  • 쿼리/뮤테이션은 dehydrated되고 제공한 persister에 저장됩니다.
  • createSyncStoragePersistercreateAsyncStoragePersister는 비용이 많이 들 수 있는 쓰기 작업을 줄이기 위해 이 동작이 최대 1초에 한 번만 발생하도록 제한합니다. 제한 주기를 사용자 지정하는 방법은 해당 문서를 확인하세요.

이를 사용하면 선택한 시점에 캐시를 명시적으로 영구 저장할 수 있습니다.

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

persistQueryClientSubscribe

queryClient의 캐시가 변경될 때마다 persistQueryClientSave를 실행합니다. 예: 사용자가 로그인하고 "로그인 상태 유지"를 선택할 때 subscribe를 시작할 수 있습니다.

  • 영구 저장된 캐시의 업데이트를 종료하여 모니터링을 중단하는 데 사용할 수 있는 unsubscribe 함수를 반환합니다.
  • unsubscribe 후에 영구 저장된 캐시를 지우려면 새 busterpersistQueryClientRestore에 전달할 수 있으며, 그러면 persister의 removeClient 함수가 실행되어 영구 저장된 캐시가 폐기됩니다.
persistQueryClientSubscribe({
queryClient,
persister,
buster = '',
dehydrateOptions = undefined,
})

persistQueryClientRestore

  • 이전에 영구 저장된 디하이드레이션된 쿼리/뮤테이션 캐시를 persister에서 전달된 query client의 쿼리 캐시로 hydrate하려고 시도합니다.
  • maxAge보다 오래된 캐시(기본값은 24시간)가 발견되면 폐기됩니다. 이 시간은 필요에 맞게 설정할 수 있습니다.

이를 사용하여 원하는 시점에 캐시를 복원할 수 있습니다.

persistQueryClientRestore({
queryClient,
persister,
maxAge = 1000 * 60 * 60 * 24, // 24 hours
buster = '',
hydrateOptions = undefined,
})

persistQueryClient

다음 작업을 수행합니다:

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

이 기능은 버전 3.x부터 유지됩니다.

persistQueryClient({
queryClient,
persister,
maxAge = 1000 * 60 * 60 * 24, // 24 hours
buster = '',
hydrateOptions = undefined,
dehydrateOptions = undefined,
})

Options

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

interface PersistQueryClientOptions {
/** The QueryClient to persist */
queryClient: QueryClient
/** The Persister interface for storing and restoring the cache
* to/from a persisted location */
persister: Persister
/** The max-allowed age of the cache in milliseconds.
* If a persisted cache is found that is older than this
* time, it will be **silently** discarded
* (defaults to 24 hours) */
maxAge?: number
/** A unique string that can be used to forcefully
* invalidate existing caches if they do not share the same buster string */
buster?: string
/** The options passed to the hydrate function
* Not used on `persistQueryClientSave` or `persistQueryClientSubscribe` */
hydrateOptions?: HydrateOptions
/** The options passed to the dehydrate function
* Not used on `persistQueryClientRestore` */
dehydrateOptions?: DehydrateOptions
}

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

  • PersistedQueryClientSaveOptionspersistQueryClientSavepersistQueryClientSubscribe에 사용됩니다(hydrateOptions는 사용하지 않습니다).
  • PersistedQueryClientRestoreOptionspersistQueryClientRestore에 사용됩니다(dehydrateOptions는 사용하지 않음).
  • PersistQueryClientOptionspersistQueryClient에 사용됩니다

React와 함께 사용하기

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

하지만 모든 persister는 본질적으로 비동기이므로 복원도 비동기적으로 이루어집니다. 즉, 복원하는 동안 App을 렌더링하면 쿼리가 동시에 마운트되고 데이터를 가져오는 경우 경쟁 조건이 발생할 수 있습니다.

또한 React 컴포넌트 생명주기 외부에서 변경 사항을 구독하면 구독을 해제할 방법이 없습니다:

// 🚨 never unsubscribes from syncing
persistQueryClient({
queryClient,
persister: localStoragePersister,
})

// 🚨 happens at the same time as restoring
ReactDOM.createRoot(rootElement).render(<App />)

PersistQueryClientProvider

이 사용 사례에서는 PersistQueryClientProvider를 사용할 수 있습니다. 이 훅은 React 컴포넌트 생명 주기에 따라 올바르게 구독 및 구독 해제하고, 복원이 진행 중인 동안에는 쿼리가 가져오기를 시작하지 않도록 합니다. 하지만 쿼리는 계속 렌더링되며, 데이터가 복원될 때까지 fetchingState: 'idle'에 배치될 뿐입니다. 그런 다음 복원된 데이터가 충분히 fresh 상태가 아니라면 다시 가져오며, _initialData_도 적용됩니다. 일반적인 QueryClientProvider대신하여 사용할 수 있습니다:

import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'

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

const persister = createAsyncStoragePersister({
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가 반환되면 이를 기다리며, 그때까지 복원이 진행 중인 것으로 간주합니다
  • onError?: () => Promise<unknown> | unknown
    • 선택 사항
    • 복원 중 오류가 발생하면 호출됩니다
    • Promise가 반환되면 완료될 때까지 기다립니다

useIsRestoring

PersistQueryClientProvider를 사용하는 경우 useIsRestoring 훅을 함께 사용하여 현재 복원이 진행 중인지 확인할 수도 있습니다. useQuery 및 관련 기능도 복원과 쿼리 마운트 사이의 경쟁 조건을 방지하기 위해 내부적으로 이를 확인합니다.

영구 저장 도구

영구 저장기 인터페이스

영구 저장 처리기는 다음 인터페이스를 갖습니다:

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

영구 저장된 Client 항목은 다음 인터페이스를 따릅니다:

export interface PersistedClient {
timestamp: number
buster: string
clientState: DehydratedState
}

다음을 가져올 수 있습니다(퍼시스터를 빌드하기 위해):

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

영구 저장기 구축

원하는 방식으로 영구 저장할 수 있습니다. 다음은 Indexed DB 영구 저장기를 구축하는 방법의 예입니다. Web Storage API와 비교하면 Indexed DB는 더 빠르고 5MB보다 많은 데이터를 저장하며 직렬화가 필요하지 않습니다. 즉, DateFile 같은 Javascript 네이티브 타입을 손쉽게 저장할 수 있습니다.

import { get, set, del } from 'idb-keyval'
import {
PersistedClient,
Persister,
} from '@tanstack/react-query-persist-client'

/**
* Creates an 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)
},
} satisfies Persister
}