queryClient.getQueryData akzeptiert jetzt nur die queryKey als ArgumentqueryClient.getQueryState akzeptiert jetzt nur die queryKey als ArgumentrefetchInterval erhält nur query übergebenremove wurde aus useQuery entferntisDataEqual wurde aus useQuery entferntcacheTime in gcTime umuseErrorBoundary wurde in throwOnError umbenanntError ist jetzt der Standardtyp für Fehler anstelle von unknownprefer-query-object-syntax wurde entferntkeepPreviousData wurde zugunsten der Identitätsfunktion placeholderData entferntfocus-Ereignisnavigator.onLinecontext-Prop wurde zugunsten einer benutzerdefinierten queryClient-Instanz entferntrefetchPage wurde zugunsten von maxPages entferntdehydrate APIinitialPageParamnull von getNextPageParam oder getPreviousPageParam zeigt jetzt an, dass keine weitere Seite verfügbar iststatus: loading wurde in status: pending geändert, und isLoading wurde in isPending geändert, und isInitialLoading wurde jetzt in isLoading umbenannthashQueryKey wurde in hashKey umbenanntcontextSharing wurde von QueryClientProvider entferntunstable_batchedUpdates wird nicht mehr als Batch-Funktion in React und React Native verwendetcombine-Option für useQueriesfine grained storage persisterv5 ist eine Hauptversion, daher gibt es einige Breaking Changes zu beachten
useQuery und verwandte Hooks hatten in TypeScript viele Überladungen: verschiedene Arten, wie die Funktion aufgerufen werden konnte. Das war nicht nur schwer zu warten, sondern erforderte auch eine Laufzeitprüfung, um zu sehen, welche Typen der erste und der zweite Parameter waren, um die Optionen korrekt zu erstellen.
Jetzt unterstützen wir nur noch das Objektformat.
useQuery(key, fn, options) // [!code --]
useQuery({ queryKey, queryFn, ...options }) // [!code ++]
useInfiniteQuery(key, fn, options) // [!code --]
useInfiniteQuery({ queryKey, queryFn, ...options }) // [!code ++]
useMutation(fn, options) // [!code --]
useMutation({ mutationFn, ...options }) // [!code ++]
useIsFetching(key, filters) // [!code --]
useIsFetching({ queryKey, ...filters }) // [!code ++]
useIsMutating(key, filters) // [!code --]
useIsMutating({ mutationKey, ...filters }) // [!code ++]
useQuery(key, fn, options) // [!code --]
useQuery({ queryKey, queryFn, ...options }) // [!code ++]
useInfiniteQuery(key, fn, options) // [!code --]
useInfiniteQuery({ queryKey, queryFn, ...options }) // [!code ++]
useMutation(fn, options) // [!code --]
useMutation({ mutationFn, ...options }) // [!code ++]
useIsFetching(key, filters) // [!code --]
useIsFetching({ queryKey, ...filters }) // [!code ++]
useIsMutating(key, filters) // [!code --]
useIsMutating({ mutationKey, ...filters }) // [!code ++]
queryClient.isFetching(key, filters) // [!code --]
queryClient.isFetching({ queryKey, ...filters }) // [!code ++]
queryClient.ensureQueryData(key, filters) // [!code --]
queryClient.ensureQueryData({ queryKey, ...filters }) // [!code ++]
queryClient.getQueriesData(key, filters) // [!code --]
queryClient.getQueriesData({ queryKey, ...filters }) // [!code ++]
queryClient.setQueriesData(key, updater, filters, options) // [!code --]
queryClient.setQueriesData({ queryKey, ...filters }, updater, options) // [!code ++]
queryClient.removeQueries(key, filters) // [!code --]
queryClient.removeQueries({ queryKey, ...filters }) // [!code ++]
queryClient.resetQueries(key, filters, options) // [!code --]
queryClient.resetQueries({ queryKey, ...filters }, options) // [!code ++]
queryClient.cancelQueries(key, filters, options) // [!code --]
queryClient.cancelQueries({ queryKey, ...filters }, options) // [!code ++]
queryClient.invalidateQueries(key, filters, options) // [!code --]
queryClient.invalidateQueries({ queryKey, ...filters }, options) // [!code ++]
queryClient.refetchQueries(key, filters, options) // [!code --]
queryClient.refetchQueries({ queryKey, ...filters }, options) // [!code ++]
queryClient.fetchQuery(key, fn, options) // [!code --]
queryClient.fetchQuery({ queryKey, queryFn, ...options }) // [!code ++]
queryClient.prefetchQuery(key, fn, options) // [!code --]
queryClient.prefetchQuery({ queryKey, queryFn, ...options }) // [!code ++]
queryClient.fetchInfiniteQuery(key, fn, options) // [!code --]
queryClient.fetchInfiniteQuery({ queryKey, queryFn, ...options }) // [!code ++]
queryClient.prefetchInfiniteQuery(key, fn, options) // [!code --]
queryClient.prefetchInfiniteQuery({ queryKey, queryFn, ...options }) // [!code ++]
queryClient.isFetching(key, filters) // [!code --]
queryClient.isFetching({ queryKey, ...filters }) // [!code ++]
queryClient.ensureQueryData(key, filters) // [!code --]
queryClient.ensureQueryData({ queryKey, ...filters }) // [!code ++]
queryClient.getQueriesData(key, filters) // [!code --]
queryClient.getQueriesData({ queryKey, ...filters }) // [!code ++]
queryClient.setQueriesData(key, updater, filters, options) // [!code --]
queryClient.setQueriesData({ queryKey, ...filters }, updater, options) // [!code ++]
queryClient.removeQueries(key, filters) // [!code --]
queryClient.removeQueries({ queryKey, ...filters }) // [!code ++]
queryClient.resetQueries(key, filters, options) // [!code --]
queryClient.resetQueries({ queryKey, ...filters }, options) // [!code ++]
queryClient.cancelQueries(key, filters, options) // [!code --]
queryClient.cancelQueries({ queryKey, ...filters }, options) // [!code ++]
queryClient.invalidateQueries(key, filters, options) // [!code --]
queryClient.invalidateQueries({ queryKey, ...filters }, options) // [!code ++]
queryClient.refetchQueries(key, filters, options) // [!code --]
queryClient.refetchQueries({ queryKey, ...filters }, options) // [!code ++]
queryClient.fetchQuery(key, fn, options) // [!code --]
queryClient.fetchQuery({ queryKey, queryFn, ...options }) // [!code ++]
queryClient.prefetchQuery(key, fn, options) // [!code --]
queryClient.prefetchQuery({ queryKey, queryFn, ...options }) // [!code ++]
queryClient.fetchInfiniteQuery(key, fn, options) // [!code --]
queryClient.fetchInfiniteQuery({ queryKey, queryFn, ...options }) // [!code ++]
queryClient.prefetchInfiniteQuery(key, fn, options) // [!code --]
queryClient.prefetchInfiniteQuery({ queryKey, queryFn, ...options }) // [!code ++]
queryCache.find(key, filters) // [!code --]
queryCache.find({ queryKey, ...filters }) // [!code ++]
queryCache.findAll(key, filters) // [!code --]
queryCache.findAll({ queryKey, ...filters }) // [!code ++]
queryCache.find(key, filters) // [!code --]
queryCache.find({ queryKey, ...filters }) // [!code ++]
queryCache.findAll(key, filters) // [!code --]
queryCache.findAll({ queryKey, ...filters }) // [!code ++]
Das Argument queryClient.getQueryData wurde geändert, um nur eine queryKey zu akzeptieren
queryClient.getQueryData(queryKey, filters) // [!code --]
queryClient.getQueryData(queryKey) // [!code ++]
queryClient.getQueryData(queryKey, filters) // [!code --]
queryClient.getQueryData(queryKey) // [!code ++]
Das Argument queryClient.getQueryState wurde geändert, um nur eine queryKey zu akzeptieren
queryClient.getQueryState(queryKey, filters) // [!code --]
queryClient.getQueryState(queryKey) // [!code ++]
queryClient.getQueryState(queryKey, filters) // [!code --]
queryClient.getQueryState(queryKey) // [!code ++]
Um die Migration der remove-Überladungen zu erleichtern, bietet v5 einen Codemod.
Der Codemod ist ein Best-Efforts-Versuch, Ihnen bei der Migration von Breaking Changes zu helfen. Bitte überprüfen Sie den generierten Code gründlich! Außerdem gibt es Randfälle, die der Code-Mod nicht finden kann, also achten Sie bitte auf die Log-Ausgabe.
Wenn Sie ihn auf Dateien mit der Endung .js oder .jsx anwenden möchten, verwenden Sie den folgenden Befehl
npx jscodeshift@latest ./path/to/src/ \
--extensions=js,jsx \
--transform=./node_modules/@tanstack/react-query/build/codemods/src/v5/remove-overloads/remove-overloads.cjs
npx jscodeshift@latest ./path/to/src/ \
--extensions=js,jsx \
--transform=./node_modules/@tanstack/react-query/build/codemods/src/v5/remove-overloads/remove-overloads.cjs
Wenn Sie ihn auf Dateien mit der Endung .ts oder .tsx anwenden möchten, verwenden Sie den folgenden Befehl
npx jscodeshift@latest ./path/to/src/ \
--extensions=ts,tsx \
--parser=tsx \
--transform=./node_modules/@tanstack/react-query/build/codemods/src/v5/remove-overloads/remove-overloads.cjs
npx jscodeshift@latest ./path/to/src/ \
--extensions=ts,tsx \
--parser=tsx \
--transform=./node_modules/@tanstack/react-query/build/codemods/src/v5/remove-overloads/remove-overloads.cjs
Bitte beachten Sie, dass Sie im Fall von TypeScript tsx als Parser verwenden müssen, andernfalls wird der Codemod nicht richtig angewendet!
Hinweis: Die Anwendung des Codemods kann Ihre Codeformatierung beeinträchtigen. Vergessen Sie also nicht, prettier und/oder eslint auszuführen, nachdem Sie den Codemod angewendet haben!
Ein paar Hinweise, wie der Codemod funktioniert
onSuccess, onError und onSettled wurden aus Queries entfernt. Sie wurden für Mutationen nicht angefasst. Bitte siehe dieses RFC für die Motivation hinter dieser Änderung und was stattdessen zu tun ist.
Dies optimiert die Art und Weise, wie Callbacks aufgerufen werden (die Callbacks refetchOnWindowFocus, refetchOnMount und refetchOnReconnect erhalten alle ebenfalls nur die Query übergeben), und es behebt einige Typisierungsprobleme, wenn Callbacks Daten erhalten, die von select transformiert wurden.
- refetchInterval: number | false | ((data: TData | undefined, query: Query) => number | false | undefined) // [!code --]
+ refetchInterval: number | false | ((query: Query) => number | false | undefined) // [!code ++]
- refetchInterval: number | false | ((data: TData | undefined, query: Query) => number | false | undefined) // [!code --]
+ refetchInterval: number | false | ((query: Query) => number | false | undefined) // [!code ++]
Sie können immer noch auf Daten über query.state.data zugreifen, aber es werden keine Daten sein, die von select transformiert wurden. Wenn Sie auf die transformierten Daten zugreifen müssen, können Sie die Transformation erneut auf query.state.data anwenden.
Zuvor entfernte die remove-Methode die Query aus der queryCache, ohne Observer darüber zu informieren. Sie wurde am besten verwendet, um Daten imperativ zu entfernen, die nicht mehr benötigt wurden, z. B. beim Abmelden eines Benutzers.
Aber es macht wenig Sinn, dies zu tun, während eine Query noch aktiv ist, da dies beim nächsten Re-Rendering einfach einen harten Ladezustand auslöst.
Wenn Sie immer noch eine Query entfernen müssen, können Sie queryClient.removeQueries({queryKey: key}) verwenden
const queryClient = useQueryClient()
const query = useQuery({ queryKey, queryFn })
query.remove() // [!code --]
queryClient.removeQueries({ queryKey }) // [!code ++]
const queryClient = useQueryClient()
const query = useQuery({ queryKey, queryFn })
query.remove() // [!code --]
queryClient.removeQueries({ queryKey }) // [!code ++]
Hauptsächlich, weil ein wichtiger Fix bezüglich der Typinferenz veröffentlicht wurde. Weitere Informationen finden Sie in diesem TypeScript-Problem.
Zuvor wurde diese Funktion verwendet, um anzugeben, ob die vorherigen Daten (true) oder die neuen Daten (false) als aufgelöste Daten für die Query verwendet werden sollten.
Sie können die gleiche Funktionalität erreichen, indem Sie stattdessen eine Funktion an structuralSharing übergeben
import { replaceEqualDeep } from '@tanstack/react-query'
- isDataEqual: (oldData, newData) => customCheck(oldData, newData) // [!code --]
+ structuralSharing: (oldData, newData) => customCheck(oldData, newData) ? oldData : replaceEqualDeep(oldData, newData) // [!code ++]
import { replaceEqualDeep } from '@tanstack/react-query'
- isDataEqual: (oldData, newData) => customCheck(oldData, newData) // [!code --]
+ structuralSharing: (oldData, newData) => customCheck(oldData, newData) ? oldData : replaceEqualDeep(oldData, newData) // [!code ++]
Benutzerdefinierte Logger waren bereits in v4 veraltet und wurden in dieser Version entfernt. Das Logging hatte nur Auswirkungen im Entwicklungsmodus, wo die Übergabe eines benutzerdefinierten Loggers nicht notwendig ist.
Wir haben unsere Browserlist aktualisiert, um ein moderneres, performanteres und kleineres Bundle zu erzeugen. Die Anforderungen können Sie hier nachlesen.
TanStack Query hatte schon immer private Felder und Methoden in Klassen, aber sie waren nicht wirklich privat – sie waren nur in TypeScript privat. Wir verwenden jetzt ECMAScript Private Class Fields, was bedeutet, dass diese Felder jetzt wirklich privat sind und zur Laufzeit nicht von außen zugegriffen werden können.
Fast jeder versteht cacheTime falsch. Es klingt wie "die Zeit, in der Daten gecacht werden", aber das ist nicht korrekt.
cacheTime tut nichts, solange eine Query noch in Gebrauch ist. Sie greift erst ein, sobald die Query nicht mehr genutzt wird. Nach Ablauf der Zeit werden Daten "garbage collected", um zu verhindern, dass der Cache wächst.
gc bezieht sich auf die "Garbage Collect"-Zeit. Es ist etwas technischer, aber auch eine ziemlich bekannte Abkürzung in der Informatik.
const MINUTE = 1000 * 60;
const queryClient = new QueryClient({
defaultOptions: {
queries: {
- cacheTime: 10 * MINUTE, // [!code --]
+ gcTime: 10 * MINUTE, // [!code ++]
},
},
})
const MINUTE = 1000 * 60;
const queryClient = new QueryClient({
defaultOptions: {
queries: {
- cacheTime: 10 * MINUTE, // [!code --]
+ gcTime: 10 * MINUTE, // [!code ++]
},
},
})
Um die Option useErrorBoundary Framework-unabhängiger zu gestalten und Verwechslungen mit dem etablierten React-Funktionspräfix "use" für Hooks und dem Komponentennamen "ErrorBoundary" zu vermeiden, wurde sie in throwOnError umbenannt, um ihre Funktionalität genauer widerzuspiegeln.
Obwohl man in JavaScript alles throwen kann (was unknown zum korrektesten Typ macht), werden fast immer Errors (oder Unterklassen von Error) geworfen. Diese Änderung erleichtert in den meisten Fällen die Arbeit mit dem error-Feld in TypeScript.
Wenn Sie etwas werfen möchten, das kein Error ist, müssen Sie jetzt den generischen Typ selbst festlegen
useQuery<number, string>({
queryKey: ['some-query'],
queryFn: async () => {
if (Math.random() > 0.5) {
throw 'some error'
}
return 42
},
})
useQuery<number, string>({
queryKey: ['some-query'],
queryFn: async () => {
if (Math.random() > 0.5) {
throw 'some error'
}
return 42
},
})
Für eine Möglichkeit, einen anderen Fehlertyp global festzulegen, siehe den TypeScript-Leitfaden.
Da nun nur noch die Objektsyntax unterstützt wird, ist diese Regel nicht mehr erforderlich
keepPreviousData wurde zugunsten der Identitätsfunktion placeholderData entferntWir haben die Option keepPreviousData und das Flag isPreviousData entfernt, da sie größtenteils das Gleiche taten wie placeholderData und das Flag isPlaceholderData.
Um die gleiche Funktionalität wie keepPreviousData zu erreichen, haben wir die vorherigen Query-Daten als Argument für placeholderData hinzugefügt, das eine Identitätsfunktion akzeptiert. Daher müssen Sie nur eine Identitätsfunktion für placeholderData angeben oder die enthaltene Funktion keepPreviousData von Tanstack Query verwenden.
Ein Hinweis hierbei ist, dass useQueries previousData in der placeholderData-Funktion nicht als Argument erhält. Dies liegt an der dynamischen Natur der im Array übergebenen Queries, was zu einer anderen Form des Ergebnisses als bei placeholder und queryFn führen kann.
import {
useQuery,
+ keepPreviousData // [!code ++]
} from "@tanstack/react-query";
const {
data,
- isPreviousData, // [!code --]
+ isPlaceholderData, // [!code ++]
} = useQuery({
queryKey,
queryFn,
- keepPreviousData: true, // [!code --]
+ placeholderData: keepPreviousData // [!code ++]
});
import {
useQuery,
+ keepPreviousData // [!code ++]
} from "@tanstack/react-query";
const {
data,
- isPreviousData, // [!code --]
+ isPlaceholderData, // [!code ++]
} = useQuery({
queryKey,
queryFn,
- keepPreviousData: true, // [!code --]
+ placeholderData: keepPreviousData // [!code ++]
});
Eine Identitätsfunktion bezieht sich im Kontext von Tanstack Query auf eine Funktion, die immer ihr übergebenes Argument (d.h. Daten) unverändert zurückgibt.
useQuery({
queryKey,
queryFn,
placeholderData: (previousData, previousQuery) => previousData, // identity function with the same behaviour as `keepPreviousData`
})
useQuery({
queryKey,
queryFn,
placeholderData: (previousData, previousQuery) => previousData, // identity function with the same behaviour as `keepPreviousData`
})
Es gibt jedoch einige Vorbehalte bei dieser Änderung, die Sie beachten müssen
placeholderData versetzt Sie immer in den success-Status, während keepPreviousData Ihnen den Status der vorherigen Query gab. Dieser Status könnte error sein, wenn wir Daten erfolgreich abgerufen und dann einen Hintergrund-Refetch-Fehler erhalten haben. Der Fehler selbst wurde jedoch nicht geteilt, daher entschieden wir uns, beim Verhalten von placeholderData zu bleiben.
keepPreviousData gab Ihnen den dataUpdatedAt-Zeitstempel der vorherigen Daten, während mit placeholderData dataUpdatedAt bei 0 bleibt. Dies kann ärgerlich sein, wenn Sie diesen Zeitstempel kontinuierlich auf dem Bildschirm anzeigen möchten. Sie können dies jedoch mit useEffect umgehen.
const [updatedAt, setUpdatedAt] = useState(0)
const { data, dataUpdatedAt } = useQuery({
queryKey: ['projects', page],
queryFn: () => fetchProjects(page),
})
useEffect(() => {
if (dataUpdatedAt > updatedAt) {
setUpdatedAt(dataUpdatedAt)
}
}, [dataUpdatedAt])
const [updatedAt, setUpdatedAt] = useState(0)
const { data, dataUpdatedAt } = useQuery({
queryKey: ['projects', page],
queryFn: () => fetchProjects(page),
})
useEffect(() => {
if (dataUpdatedAt > updatedAt) {
setUpdatedAt(dataUpdatedAt)
}
}, [dataUpdatedAt])
Das visibilitychange-Ereignis wird jetzt ausschließlich verwendet. Dies ist möglich, da wir nur Browser unterstützen, die das visibilitychange-Ereignis unterstützen. Dies behebt eine Reihe von Problemen, wie hier aufgelistet.
navigator.onLine funktioniert in Chromium-basierten Browsern nicht gut. Es gibt viele Probleme mit negativen Fehlalarmen, die dazu führen, dass Queries fälschlicherweise als offline markiert werden.
Um dies zu umgehen, beginnen wir nun immer mit online: true und lauschen nur auf online und offline Ereignisse, um den Status zu aktualisieren.
Dies sollte die Wahrscheinlichkeit von falschen Negativen verringern, kann aber zu falschen Positiven für Offline-Anwendungen führen, die über ServiceWorker geladen werden und auch ohne Internetverbindung funktionieren können.
In v4 haben wir die Möglichkeit eingeführt, benutzerdefinierte context an alle react-query Hooks zu übergeben. Dies ermöglichte eine ordnungsgemäße Isolierung bei der Verwendung von MicroFrontends.
context ist jedoch nur ein React-Feature. Alles, was context tut, ist uns den Zugriff auf den queryClient zu ermöglichen. Wir könnten die gleiche Isolierung erreichen, indem wir zulassen, dass ein benutzerdefinierter queryClient direkt übergeben wird. Dies wird es anderen Frameworks ermöglichen, die gleiche Funktionalität auf Framework-unabhängige Weise zu haben.
import { queryClient } from './my-client'
const { data } = useQuery(
{
queryKey: ['users', id],
queryFn: () => fetch(...),
- context: customContext // [!code --]
},
+ queryClient, // [!code ++]
)
import { queryClient } from './my-client'
const { data } = useQuery(
{
queryKey: ['users', id],
queryFn: () => fetch(...),
- context: customContext // [!code --]
},
+ queryClient, // [!code ++]
)
refetchPage wurde zugunsten von maxPages entferntIn v4 haben wir die Möglichkeit eingeführt, die Seiten für Infinite Queries mit der Funktion refetchPage zu definieren.
Das erneute Abrufen aller Seiten kann jedoch zu UI-Inkonsistenzen führen. Außerdem ist diese Option z. B. bei queryClient.refetchQueries verfügbar, aber sie bewirkt nur etwas für Infinite Queries, nicht für "normale" Queries.
v5 enthält eine neue Option maxPages für Infinite Queries, um die Anzahl der Seiten zu begrenzen, die in den Query-Daten gespeichert und erneut abgerufen werden. Dieses neue Feature deckt die Anwendungsfälle ab, die ursprünglich für die refetchPage-Seitenfunktion identifiziert wurden, ohne die damit verbundenen Probleme.
Die Optionen, die Sie an dehydrate übergeben können, wurden vereinfacht. Queries und Mutationen werden immer dehydriert (gemäß der Standardfunktionsimplementierung). Um dieses Verhalten zu ändern, können Sie anstelle der entfernten booleschen Optionen dehydrateMutations und dehydrateQueries die Funktionsäquivalente shouldDehydrateQuery oder shouldDehydrateMutation implementieren. Um das alte Verhalten, Queries/Mutationen überhaupt nicht zu hydrieren, zu erhalten, übergeben Sie () => false.
- dehydrateMutations?: boolean // [!code --]
- dehydrateQueries?: boolean // [!code --]
- dehydrateMutations?: boolean // [!code --]
- dehydrateQueries?: boolean // [!code --]
Zuvor haben wir undefined an die queryFn als pageParam übergeben, und Sie konnten einen Standardwert für den Parameter pageParam in der queryFn-Funktionssignatur zuweisen. Dies hatte den Nachteil, dass undefined in der queryCache gespeichert wurde, was nicht serialisierbar ist.
Stattdessen müssen Sie jetzt eine explizite initialPageParam an die Infinite Query-Optionen übergeben. Diese wird als pageParam für die erste Seite verwendet
useInfiniteQuery({
queryKey,
- queryFn: ({ pageParam = 0 }) => fetchSomething(pageParam), // [!code --]
+ queryFn: ({ pageParam }) => fetchSomething(pageParam), // [!code ++]
+ initialPageParam: 0, // [!code ++]
getNextPageParam: (lastPage) => lastPage.next,
})
useInfiniteQuery({
queryKey,
- queryFn: ({ pageParam = 0 }) => fetchSomething(pageParam), // [!code --]
+ queryFn: ({ pageParam }) => fetchSomething(pageParam), // [!code ++]
+ initialPageParam: 0, // [!code ++]
getNextPageParam: (lastPage) => lastPage.next,
})
Zuvor erlaubten wir, die pageParams, die von getNextPageParam oder getPreviousPageParam zurückgegeben wurden, zu überschreiben, indem ein pageParam-Wert direkt an fetchNextPage oder fetchPreviousPage übergeben wurde. Dieses Feature funktionierte bei Refetches überhaupt nicht und war nicht allgemein bekannt oder genutzt. Das bedeutet auch, dass getNextPageParam für Infinite Queries jetzt erforderlich ist.
In v4 mussten Sie explizit undefined zurückgeben, um anzuzeigen, dass keine weitere Seite verfügbar ist. Wir haben diese Prüfung erweitert, um auch null einzuschließen.
Auf dem Server hat retry jetzt standardmäßig 0 statt 3. Für Prefetching haben wir schon immer standardmäßig 0 Wiederholungsversuche verwendet, aber da Queries mit suspense jetzt auch auf dem Server ausgeführt werden können (seit React18), müssen wir sicherstellen, dass wir auf dem Server überhaupt keine Wiederholungsversuche durchführen.
status: loading wurde in status: pending geändert, und isLoading wurde in isPending geändert, und isInitialLoading wurde jetzt in isLoading umbenanntDer loading-Status wurde in pending umbenannt, und ebenso wurde das abgeleitete Flag isLoading in isPending umbenannt.
Auch für Mutationen wurde der status von loading in pending geändert und das isLoading-Flag wurde in isPending geändert.
Zuletzt wurde ein neues abgeleitetes Flag isLoading zu den Queries hinzugefügt, das als isPending && isFetching implementiert ist. Das bedeutet, dass isLoading und isInitialLoading dasselbe bedeuten, aber isInitialLoading nun veraltet ist und in der nächsten Hauptversion entfernt wird.
Um die Gründe für diese Änderung zu verstehen, schauen Sie sich die v5 Roadmap-Diskussion an.
hashQueryKey wurde in hashKey umbenanntda es auch Mutationsschlüssel hasht und innerhalb der predicate-Funktionen von useIsMutating und useMutationState verwendet werden kann, denen Mutationen übergeben werden.
React Query v5 erfordert React 18.0 oder höher. Dies liegt daran, dass wir den neuen useSyncExternalStore Hook verwenden, der nur in React 18.0 und höher verfügbar ist. Zuvor haben wir das von React bereitgestellte Shim verwendet.
Sie konnten zuvor die Eigenschaft contextSharing verwenden, um die erste (und mindestens eine) Instanz des Query-Client-Kontextes über das Fenster hinweg zu teilen. Dies stellte sicher, dass, wenn TanStack Query über verschiedene Bundles oder Microfrontends hinweg verwendet wurde, sie alle die gleiche Instanz des Kontextes verwendeten, unabhängig von der Modul-Scope.
Mit der Entfernung der benutzerdefinierten Kontext-Prop in v5, siehe den Abschnitt über Entfernte benutzerdefinierte Kontext-Prop zugunsten einer benutzerdefinierten queryClient-Instanz. Wenn Sie denselben Query-Client über mehrere Pakete einer Anwendung hinweg teilen möchten, können Sie direkt eine geteilte benutzerdefinierte queryClient-Instanz übergeben.
unstable_batchedUpdates wird nicht mehr als Batch-Funktion in React und React Native verwendetDa die Funktion unstable_batchedUpdates in React 18 ein Noop ist, wird sie nicht mehr automatisch als Batch-Funktion in react-query gesetzt.
Wenn Ihr Framework eine benutzerdefinierte Batch-Funktion unterstützt, können Sie TanStack Query dies mitteilen, indem Sie notifyManager.setBatchNotifyFunction aufrufen.
Zum Beispiel wird die Batch-Funktion in solid-query so gesetzt
import { notifyManager } from '@tanstack/query-core'
import { batch } from 'solid-js'
notifyManager.setBatchNotifyFunction(batch)
import { notifyManager } from '@tanstack/query-core'
import { batch } from 'solid-js'
notifyManager.setBatchNotifyFunction(batch)
Um Nebenläufigkeitsfeatures und Übergänge besser zu unterstützen, haben wir einige Änderungen an den Hydrations-APIs vorgenommen. Die Komponente Hydrate wurde in HydrationBoundary umbenannt und der Hook useHydrate wurde entfernt.
Die HydrationBoundary hydriert keine Mutationen mehr, nur noch Queries. Um Mutationen zu hydrieren, verwenden Sie die Low-Level hydrate API oder das Plugin persistQueryClient.
Schließlich haben sich als technische Details die Zeitpunkte, zu denen Queries hydriert werden, leicht geändert. Neue Queries werden weiterhin in der Render-Phase hydriert, damit SSR wie gewohnt funktioniert. Jede Query, die sich bereits im Cache befindet, wird jetzt jedoch in einem Effekt hydriert (solange ihre Daten frischer sind als die im Cache befindlichen). Wenn Sie nur einmal am Anfang Ihrer Anwendung hydrieren, wie es üblich ist, wird sich dies nicht auf Sie auswirken. Wenn Sie jedoch Server Components verwenden und beim Navigieren zu einer Seite frische Daten zur Hydrierung übergeben, sehen Sie möglicherweise einen kurzen Moment alte Daten, bevor die Seite sofort neu gerendert wird.
Diese letzte Änderung ist technisch gesehen eine Breaking Change und wurde vorgenommen, damit wir Inhalte auf der *bestehenden* Seite nicht vorzeitig aktualisieren, bevor ein Seitenübergang vollständig abgeschlossen ist. Es sind keine Maßnahmen Ihrerseits erforderlich.
- import { Hydrate } from '@tanstack/react-query' // [!code --]
+ import { HydrationBoundary } from '@tanstack/react-query' // [!code ++]
- <Hydrate state={dehydratedState}> // [!code --]
+ <HydrationBoundary state={dehydratedState}> // [!code ++]
<App />
- </Hydrate> // [!code --]
+ </HydrationBoundary> // [!code ++]
- import { Hydrate } from '@tanstack/react-query' // [!code --]
+ import { HydrationBoundary } from '@tanstack/react-query' // [!code ++]
- <Hydrate state={dehydratedState}> // [!code --]
+ <HydrationBoundary state={dehydratedState}> // [!code ++]
<App />
- </Hydrate> // [!code --]
+ </HydrationBoundary> // [!code ++]
queryClient.getQueryDefaults wird jetzt alle übereinstimmenden Registrierungen zusammenführen, anstatt nur die erste übereinstimmende Registrierung zurückzugeben.
Infolgedessen sollten Aufrufe von queryClient.setQueryDefaults jetzt mit *zunehmender* Spezifität geordnet werden. Das heißt, Registrierungen sollten vom **allgemeinsten Schlüssel** zum **am wenigsten generischen** erfolgen.
Zum Beispiel
+ queryClient.setQueryDefaults(['todo'], { // [!code ++]
+ retry: false, // [!code ++]
+ staleTime: 60_000, // [!code ++]
+ }) // [!code ++]
queryClient.setQueryDefaults(['todo', 'detail'], {
+ retry: true, // [!code --]
retryDelay: 1_000,
staleTime: 10_000,
})
- queryClient.setQueryDefaults(['todo'], { // [!code --]
- retry: false, // [!code --]
- staleTime: 60_000, // [!code --]
- }) // [!code --]
+ queryClient.setQueryDefaults(['todo'], { // [!code ++]
+ retry: false, // [!code ++]
+ staleTime: 60_000, // [!code ++]
+ }) // [!code ++]
queryClient.setQueryDefaults(['todo', 'detail'], {
+ retry: true, // [!code --]
retryDelay: 1_000,
staleTime: 10_000,
})
- queryClient.setQueryDefaults(['todo'], { // [!code --]
- retry: false, // [!code --]
- staleTime: 60_000, // [!code --]
- }) // [!code --]
Beachten Sie, dass in diesem spezifischen Beispiel retry: true zur Registrierung von ['todo', 'detail'] hinzugefügt wurde, um zu verhindern, dass es nun retry: false von der allgemeineren Registrierung erbt. Die spezifischen Änderungen, die zur Beibehaltung des genauen Verhaltens erforderlich sind, variieren je nach Ihren Standardwerten.
v5 kommt auch mit neuen Funktionen
Wir haben eine neue, vereinfachte Möglichkeit, optimistische Updates durchzuführen, indem wir die zurückgegebenen variables von useMutation nutzen
const queryInfo = useTodos()
const addTodoMutation = useMutation({
mutationFn: (newTodo: string) => axios.post('/api/data', { text: newTodo }),
onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
})
if (queryInfo.data) {
return (
<ul>
{queryInfo.data.items.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
{addTodoMutation.isPending && (
<li key={String(addTodoMutation.submittedAt)} style={{ opacity: 0.5 }}>
{addTodoMutation.variables}
</li>
)}
</ul>
)
}
const queryInfo = useTodos()
const addTodoMutation = useMutation({
mutationFn: (newTodo: string) => axios.post('/api/data', { text: newTodo }),
onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
})
if (queryInfo.data) {
return (
<ul>
{queryInfo.data.items.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
{addTodoMutation.isPending && (
<li key={String(addTodoMutation.submittedAt)} style={{ opacity: 0.5 }}>
{addTodoMutation.variables}
</li>
)}
</ul>
)
}
Hier ändern wir nur, wie die Benutzeroberfläche aussieht, wenn die Mutation läuft, anstatt Daten direkt in den Cache zu schreiben. Dies funktioniert am besten, wenn wir nur einen Ort haben, an dem wir das optimistische Update anzeigen müssen. Weitere Details finden Sie in der Dokumentation zu optimistischen Updates.
Unendliche Abfragen sind großartig, wenn unendliches Scrollen oder Paginierung benötigt werden. Je mehr Seiten Sie jedoch abrufen, desto mehr Speicher verbrauchen Sie, und dies verlangsamt auch den Prozess des erneuten Abrufens von Abfragen, da alle Seiten sequenziell erneut abgerufen werden.
Version 5 hat eine neue Option maxPages für unendliche Abfragen, mit der Entwickler die Anzahl der Seiten begrenzen können, die in den Abfragedaten gespeichert und anschließend erneut abgerufen werden. Sie können den Wert maxPages an die UX und die Leistung beim erneuten Abrufen anpassen, die Sie liefern möchten.
Beachten Sie, dass die unendliche Liste bidirektional sein muss, was erfordert, dass sowohl getNextPageParam als auch getPreviousPageParam definiert sind.
Unendliche Abfragen können wie normale Abfragen vorab abgerufen werden. Standardmäßig wird nur die erste Seite der Abfrage vorab abgerufen und unter dem angegebenen Abfrageschlüssel gespeichert. Wenn Sie mehr als eine Seite vorab abrufen möchten, können Sie die Option pages verwenden. Lesen Sie das Handbuch zum Vorabrufen für weitere Informationen.
Weitere Details finden Sie in der Dokumentation zu useQueries.
Weitere Details finden Sie in der Dokumentation zu experimental_createPersister.
Weitere Details finden Sie in der TypeScript-Dokumentation.
Mit v5 wird Suspense für das Abrufen von Daten endlich "stabil". Wir haben dedizierte Hooks hinzugefügt: useSuspenseQuery, useSuspenseInfiniteQuery und useSuspenseQueries. Mit diesen Hooks ist data auf Typenebene nie mehr potenziell undefined
const { data: post } = useSuspenseQuery({
// ^? const post: Post
queryKey: ['post', postId],
queryFn: () => fetchPost(postId),
})
const { data: post } = useSuspenseQuery({
// ^? const post: Post
queryKey: ['post', postId],
queryFn: () => fetchPost(postId),
})
Das experimentelle Flag suspense: boolean bei den Abfrage-Hooks wurde entfernt.
Mehr dazu erfahren Sie in der Suspense-Dokumentation.