persistQueryClient
이 유틸리티 세트는 "persisters"와 상호작용하여 queryClient를 나중에 사용할 수 있도록 저장하는 데 도움을 줍니다. 다양한 persisters를 사용하여 클라이언트와 캐시를 여러 저장소 계층에 저장할 수 있습니다.
Build Persisters
How It Works
중요 - persistence가 제대로 작동하려면 QueryClient
에 gcTime
값을 설정하여 hydration 중 기본값을 덮어쓰는 것이 좋습니다 (위 예제 참조).
QueryClient
인스턴스를 생성할 때 gcTime
이 설정되지 않으면 기본값인 300000
(5분)으로 설정되며, 5분 동안 비활성 상태가 되면 저장된 캐시가 삭제됩니다. 이것이 기본적인 garbage collection 동작입니다.
이 값은 persistQueryClient
의 maxAge
옵션과 동일하거나 더 높은 값으로 설정해야 합니다. 예를 들어 maxAge
가 24시간(기본값)인 경우 gcTime
도 24시간 이상으로 설정해야 합니다. maxAge
보다 낮으면 garbage collection이 작동하여 예상보다 일찍 저장된 캐시를 삭제할 수 있습니다.
gcTime
을 Infinity
로 설정하여 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
데이터가 다음 중 하나로 판명된 경우:
- 만료된 경우 (see
maxAge
) - 무효화된 경우 (see
buster
) - 오류가 발생한 경우 (예:
throws ...
) - 비어 있는 경우 (예:
undefined
)
removeClient()
가 호출되고 캐시가 즉시 삭제됩니다.
API
persistQueryClientSave
- 쿼리/변경 사항이
dehydrated
되고 제공한 persister에 의해 저장됩니다. createSyncStoragePersister
와createAsyncStoragePersister
는 이 작업이 최대 1초마다 한 번만 수행되도록 조절하여 잠재적으로 비용이 많이 드는 쓰기를 줄입니다. 조정 가능한 throttle timing에 대한 문서를 검토하십시오.
원하는 순간에 캐시를 명시적으로 저장하는 데 사용할 수 있습니다.
persistQueryClientSave({
queryClient,
persister,
buster = "",
dehydrateOptions = undefined,
});
persistQueryClientSubscribe
persistQueryClientSave
를 사용하여 queryClient
의 캐시가 변경될 때마다 자동으로 저장합니다. 예를 들어, 사용자가 로그인할 때 "Remember me"를 체크하면 subscribe
를 시작할 수 있습니다.
unsubscribe
함수를 반환하며, 이를 통해 모니터링을 중단하고 persisted cache 업데이트를 종료할 수 있습니다.unsubscribe
이후에 persisted cache를 지우고 싶다면, 새로운buster
를persistQueryClientRestore
에 전달하여 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
다음과 같은 작업을 수행합니다:
- 즉시 저장된 캐시를 복원합니다 (
persistQueryClientRestore
참조). - 쿼리 캐시에 구독하고
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
:persistQueryClientSave
및persistQueryClientSubscribe
에서 사용되며,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
PersistQueryClientProvider
는 QueryClientProvider와 동일한 props를 사용하며, 추가로:
persistOptions: PersistQueryClientOptions
persistQueryClient
에 전달할 수 있는 모든 옵션, 단 QueryClient 자체는 제외
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;
}
이 예제에서는:
persistClient
는PersistedClient
를 IndexedDB에 저장합니다.restoreClient
는 IndexedDB에서PersistedClient
를 가져옵니다.removeClient
는 IndexedDB에서PersistedClient
를 삭제합니다.
필요에 따라 다른 저장 메커니즘이나 요구 사항에 맞게 이 구현을 사용자화할 수 있습니다.