Framework
Version

Migration zu TanStack Query v5

Breaking Changes

v5 ist eine Hauptversion, daher gibt es einige Breaking Changes zu beachten

Unterstützt eine einzige Signatur, ein Objekt

useQuery und ähnliche Funktionen hatten früher viele Überladungen in TypeScript: verschiedene Möglichkeiten, wie die Funktion aufgerufen werden konnte. Dies war nicht nur schwierig zu warten, sondern erforderte auch eine Laufzeitüberprüfung, um festzustellen, welche Typen der erste und der zweite Parameter waren, um die Optionen korrekt zu erstellen.

Jetzt unterstützen wir nur noch das Objektformat.

tsx
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 ++]
tsx
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 ++]
tsx
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 ++]

queryClient.getQueryData akzeptiert jetzt nur die Query-Schlüssel als Argument

Das Argument queryClient.getQueryData wurde geändert, um nur einen queryKey zu akzeptieren

tsx
queryClient.getQueryData(queryKey, filters) // [!code --]
queryClient.getQueryData(queryKey) // [!code ++]
queryClient.getQueryData(queryKey, filters) // [!code --]
queryClient.getQueryData(queryKey) // [!code ++]

queryClient.getQueryState akzeptiert jetzt nur die Query-Schlüssel als Argument

Das Argument queryClient.getQueryState wurde geändert, um nur einen queryKey zu akzeptieren

tsx
queryClient.getQueryState(queryKey, filters) // [!code --]
queryClient.getQueryState(queryKey) // [!code ++]
queryClient.getQueryState(queryKey, filters) // [!code --]
queryClient.getQueryState(queryKey) // [!code ++]

Codemod

Um die Migration von Remove-Überladungen zu erleichtern, bietet v5 ein 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

  • Im Allgemeinen suchen wir nach dem glücklichen Fall, wenn der erste Parameter ein Objekt-Ausdruck ist und die Eigenschaft "queryKey" oder "mutationKey" enthält (abhängig davon, welcher Hook/welche Methode transformiert wird). Wenn dies der Fall ist, entspricht Ihr Code bereits der neuen Signatur, sodass der Codemod ihn nicht berührt. 🎉
  • Wenn die obige Bedingung nicht erfüllt ist, prüft der Codemod, ob der erste Parameter ein Array-Ausdruck oder ein Bezeichner ist, der auf einen Array-Ausdruck verweist. Wenn dies der Fall ist, wird er in einen Objekt-Ausdruck gestellt, der dann der erste Parameter ist.
  • Wenn Objektparameter abgeleitet werden können, versucht der Codemod, die bereits vorhandenen Eigenschaften in die neu erstellte zu kopieren.
  • Wenn der Codemod die Verwendung nicht ableiten kann, hinterlässt er eine Meldung in der Konsole. Die Meldung enthält den Dateinamen und die Zeilennummer der Verwendung. In diesem Fall müssen Sie die Migration manuell durchführen.
  • Wenn die Transformation zu einem Fehler führt, sehen Sie ebenfalls eine Meldung in der Konsole. Diese Meldung informiert Sie darüber, dass etwas Unerwartetes passiert ist, und Sie müssen die Migration manuell durchführen.

Callbacks bei useQuery (und QueryObserver) wurden entfernt

onSuccess, onError und onSettled wurden aus Queries entfernt. Sie wurden für Mutations nicht berührt. Bitte siehe dieses RFC für die Motivationen hinter dieser Änderung und was stattdessen zu tun ist.

Die Callback-Funktion refetchInterval erhält nur query übergeben

Dies optimiert die Aufrufe von Callbacks (die Callbacks refetchOnWindowFocus, refetchOnMount und refetchOnReconnect erhalten ebenfalls nur die Query übergeben) und behebt einige Tippfehlerprobleme, wenn Callbacks Daten erhalten, die durch select transformiert wurden.

tsx
- 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, allerdings werden dies nicht die durch select transformierten Daten sein. Wenn Sie auf die transformierten Daten zugreifen möchten, können Sie die Transformation erneut auf query.state.data anwenden.

Die Methode remove wurde aus useQuery entfernt

Zuvor entfernte die remove-Methode die Query aus der queryCache, ohne Beobachter 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 nicht viel Sinn, dies zu tun, während eine Abfrage noch aktiv ist, da dies bei der nächsten Neurendung nur einen harten Ladezustand auslöst.

Wenn Sie immer noch eine Abfrage entfernen müssen, können Sie queryClient.removeQueries({queryKey: key}) verwenden

tsx
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 ++]

Die Mindestanforderung an die TypeScript-Version ist jetzt 4.7

Hauptsächlich, weil ein wichtiger Fix in Bezug auf die Typinferenz ausgeliefert wurde. Bitte siehe dieses TypeScript-Problem für weitere Informationen.

Die Option isDataEqual wurde aus useQuery entfernt

Zuvor wurde diese Funktion verwendet, um anzugeben, ob frühere Daten (true) oder neue Daten (false) als aufgelöste Daten für die Abfrage verwendet werden sollten.

Sie können dieselbe Funktionalität erreichen, indem Sie stattdessen eine Funktion an structuralSharing übergeben

tsx
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 ++]

Der veraltete benutzerdefinierte Logger wurde entfernt

Benutzerdefinierte Logger waren bereits in Version 4 als veraltet markiert und wurden in dieser Version entfernt. Das Logging hatte nur Auswirkungen im Entwicklungsmodus, wo die Übergabe eines benutzerdefinierten Loggers nicht notwendig ist.

Unterstützte Browser

Wir haben unseren browserslist aktualisiert, um ein moderneres, performanteres und kleineres Bundle zu erzeugen. Sie können die Anforderungen hier lesen.

Private Klassenfelder und -methoden

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 Features, was bedeutet, dass diese Felder jetzt wirklich privat sind und zur Laufzeit nicht von außen zugänglich sind.

cacheTime umbenannt in gcTime

Fast jeder versteht cacheTime falsch. Es klingt wie "die Zeitspanne, für die Daten im Cache gespeichert werden", aber das ist nicht korrekt.

cacheTime tut nichts, solange eine Abfrage noch in Benutzung ist. Sie tritt erst in Kraft, sobald die Abfrage nicht mehr genutzt wird. Nach Ablauf der Zeit werden die 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.

tsx
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 ++]
    },
  },
})

Die Option useErrorBoundary wurde in throwOnError umbenannt

Um die Option useErrorBoundary Framework-agnostischer 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 wiederzugeben.

TypeScript: Error ist jetzt der Standardtyp für Fehler anstelle von unknown

Auch wenn 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 Fehler ist, müssen Sie jetzt den Generics selbst festlegen

ts
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
  },
})

Eine Möglichkeit, einen anderen Fehlertyp global festzulegen, finden Sie im TypeScript-Leitfaden.

Die ESLint-Regel prefer-query-object-syntax wurde entfernt

Da nun nur noch die Objekt-Syntax unterstützt wird, ist diese Regel nicht mehr erforderlich

keepPreviousData entfernt zugunsten der Identitätsfunktion placeholderData

Wir haben die Option keepPreviousData und das Flag isPreviousData entfernt, da sie größtenteils dasselbe taten wie placeholderData und das Flag isPlaceholderData.

Um die gleiche Funktionalität wie keepPreviousData zu erreichen, haben wir die previous query data als Argument zu placeholderData hinzugefügt, das eine Identitätsfunktion akzeptiert. Daher müssen Sie nur eine Identitätsfunktion an placeholderData übergeben oder die mitgelieferte keepPreviousData-Funktion von Tanstack Query verwenden.

Ein Hinweis hier ist, dass useQueries previousData in der placeholderData-Funktion nicht als Argument erhält. Dies liegt an der dynamischen Natur der übergebenen Abfragen im Array, was zu einer anderen Form des Ergebnisses von placeholder und queryFn führen kann.

tsx
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 bereitgestelltes Argument (d.h. Daten) unverändert zurückgibt.

ts
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 Abfrage gab. Dieser Status könnte error sein, wenn wir erfolgreich Daten abgerufen haben und dann einen Fehler bei der Hintergrundaktualisierung erhalten haben. Der Fehler selbst wurde jedoch nicht geteilt, daher haben wir uns entschieden, beim Verhalten von placeholderData zu bleiben.

  • keepPreviousData gab Ihnen den dataUpdatedAt-Zeitstempel der vorherigen Daten zurück, während bei 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.

    ts
    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])
    

Fensterfokus-Refetching hört nicht mehr auf das focus-Ereignis

Das visibilitychange-Ereignis wird 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 aufgeführt.

Netzwerkstatus verlässt sich nicht mehr auf die Eigenschaft navigator.onLine

navigator.onLine funktioniert in Chromium-basierten Browsern nicht gut. Es gibt viele Probleme mit falschen Negativen, die dazu führen, dass Abfragen 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.

Benutzerdefiniertes context-Prop entfernt zugunsten einer benutzerdefinierten queryClient-Instanz

In v4 haben wir die Möglichkeit eingeführt, benutzerdefinierten context an alle react-query-Hooks zu übergeben. Dies ermöglichte eine korrekte Isolierung bei der Verwendung von MicroFrontends.

context ist jedoch eine reine React-Funktion. Alles, was context tut, ist uns den Zugriff auf den queryClient zu ermöglichen. Diesen gleichen Grad an Isolierung könnten wir erreichen, indem wir erlauben, einen benutzerdefinierten queryClient direkt zu übergeben. Dies wiederum wird es anderen Frameworks ermöglichen, die gleiche Funktionalität auf eine Framework-unabhängige Weise zu erhalten.

tsx
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 entfernt zugunsten von maxPages

In v4 haben wir die Möglichkeit eingeführt, die Seiten für unendliche Abfragen 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 tut nur etwas für unendliche Abfragen, nicht für "normale" Abfragen.

Die v5 enthält eine neue Option maxPages für unendliche Abfragen, um die Anzahl der im Abfragedaten gespeicherten und anschließend abgerufenen Seiten zu begrenzen. Diese neue Funktion deckt die Anwendungsfälle ab, die ursprünglich für die refetchPage-Seitenfunktion identifiziert wurden, ohne die damit verbundenen Probleme.

Neue dehydrate API

Die Optionen, die an dehydrate übergeben werden können, wurden vereinfacht. Abfragen und Mutationen werden immer dehydriert (gemäß der Standardfunktionsimplementierung). Um dieses Verhalten zu ändern, können Sie anstelle der entfernten booleschen Optionen dehydrateMutations und dehydrateQueries stattdessen die Funktionsäquivalente shouldDehydrateQuery oder shouldDehydrateMutation implementieren. Um das alte Verhalten, Abfragen/Mutationen gar nicht zu hydrieren, zu erhalten, übergeben Sie () => false.

tsx
- dehydrateMutations?: boolean // [!code --]
- dehydrateQueries?: boolean // [!code --]
- dehydrateMutations?: boolean // [!code --]
- dehydrateQueries?: boolean // [!code --]

Unendliche Abfragen benötigen jetzt einen initialPageParam

Zuvor haben wir undefined an die queryFn als pageParam übergeben, und Sie konnten einen Standardwert dem pageParam-Parameter in der queryFn-Funktionssignatur zuweisen. Dies hatte den Nachteil, undefined im queryCache zu speichern, was nicht serialisierbar ist.

Stattdessen müssen Sie jetzt einen expliziten initialPageParam an die Optionen für unendliche Abfragen übergeben. Dieser wird als pageParam für die erste Seite verwendet

tsx
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,
})

Der manuelle Modus für unendliche Abfragen wurde entfernt

Zuvor erlaubten wir es, die von getNextPageParam oder getPreviousPageParam zurückgegebenen pageParams zu überschreiben, indem wir einen pageParam-Wert direkt an fetchNextPage oder fetchPreviousPage übergeben. Diese Funktion funktionierte bei Refetches überhaupt nicht und war nicht weithin bekannt oder genutzt. Dies bedeutet auch, dass getNextPageParam jetzt für unendliche Abfragen erforderlich ist.

Das Zurückgeben von null von getNextPageParam oder getPreviousPageParam zeigt jetzt an, dass keine weitere Seite verfügbar 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 null einzuschließen.

Keine Wiederholungsversuche auf dem Server

Auf dem Server ist retry jetzt standardmäßig 0 statt 3. Für Prefetching haben wir immer auf 0 Wiederholungsversuche gesetzt, aber da Abfragen mit suspense nun auch auf dem Server direkt ausgeführt werden können (seit React18), müssen wir sicherstellen, dass auf dem Server überhaupt keine Wiederholungsversuche stattfinden.

status: loading wurde zu status: pending geändert und isLoading wurde zu isPending geändert und isInitialLoading wurde jetzt in isLoading umbenannt

Der Status loading wurde in pending umbenannt, und ähnlich wurde das abgeleitete Flag isLoading in isPending umbenannt.

Auch für Mutationen wurde der status von loading zu pending geändert und das isLoading-Flag wurde zu isPending geändert.

Zuletzt wurde ein neues abgeleitetes Flag isLoading zu den Abfragen hinzugefügt, das als isPending && isFetching implementiert ist. Das bedeutet, dass isLoading und isInitialLoading dasselbe tun, aber isInitialLoading jetzt veraltet ist und in der nächsten Hauptversion entfernt wird.

Um die Gründe für diese Änderung zu verstehen, siehe die v5 Roadmap-Diskussion.

hashQueryKey wurde in hashKey umbenannt

weil es auch Mutationsschlüssel hashd und innerhalb der predicate-Funktionen von useIsMutating und useMutationState verwendet werden kann, die Mutationen übergeben bekommen.

Vue Query Breaking Changes

useQueries-Composable gibt jetzt ref anstelle von reactive zurück

Um die Kompatibilität mit Vue 2 zu gewährleisten, gibt das useQueries-Composable jetzt das queries-Array, das in ref verpackt ist, zurück. Zuvor wurde reactive zurückgegeben, was zu mehreren Problemen führte

  • Benutzer konnten den Rückgabewert entpacken und dabei die Reaktivität verlieren.
  • Der readonly-Wrapper, der für den Rückgabewert verwendet wurde, brach den Reaktivitäts-Erkennungsmechanismus von Vue 2. Dies war ein stilles Problem in Vue 2.6, trat aber in Vue 2.7 als Fehler auf.
  • Vue 2 unterstützt keine Arrays als Root-Wert von reactive.

Mit dieser Änderung sind all diese Probleme behoben.

Außerdem richtet dies useQueries an anderen Composables aus, die alle Werte als refs zurückgeben.

Vue v3.3 ist jetzt erforderlich

Um neue Funktionen nach den Vue-Releases bereitstellen zu können, benötigen wir jetzt mindestens Vue 3 in der Version v3.3. Die Anforderungen für Vue 2.x bleiben unverändert.

Neue Features 🚀

v5 kommt auch mit neuen Funktionen

Optimistische Updates vereinfacht

Wir haben eine neue, vereinfachte Methode zur Durchführung optimistischer Updates durch die Nutzung der zurückgegebenen variables von useMutation

tsx
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 ausgeführt wird, anstatt Daten direkt in den Cache zu schreiben. Dies funktioniert am besten, wenn wir nur einen Ort haben, an dem wir die optimistische Aktualisierung anzeigen müssen. Weitere Details finden Sie in der Dokumentation zu optimistic updates.

Begrenzte, unendliche Abfragen mit neuer Option maxPages

Unendliche Abfragen sind großartig, wenn unendliches Scrollen oder Paginierung benötigt wird. Je mehr Seiten Sie 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 verfügt über 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 maxPages-Wert an die gewünschte UX und die Leistung beim erneuten Abrufen anpassen.

Beachten Sie, dass die unendliche Liste bidirektional sein muss, was erfordert, dass sowohl getNextPageParam als auch getPreviousPageParam definiert sind.

Unendliche Abfragen können mehrere Seiten vorab abrufen

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 QueryKey gespeichert. Wenn Sie mehr als eine Seite vorab abrufen möchten, können Sie die Option pages verwenden. Lesen Sie den Leitfaden Prefetching für weitere Informationen.

Neue combine-Option für useQueries

Weitere Details finden Sie in der useQueries-Dokumentation.

Experimenteller fine grained storage persister

Weitere Details finden Sie in der Dokumentation zu experimental_createPersister.

Möglichkeit, vue-query-Composables in injectionContext auszuführen

Zuvor konnten vue-query-Composables nur innerhalb der setup-Funktion der Komponente ausgeführt werden.
Wir hatten eine Ausweichmöglichkeit, um diese Hooks überall ausführen zu lassen, wenn der Benutzer queryClient als Composable-Option bereitstellte.

Jetzt können Sie vue-query-Composables in jeder Funktion verwenden, die injectionContext unterstützt. z.B. Router-Navigationswächter. Wenn Sie diese neue Funktion verwenden, stellen Sie sicher, dass das vue-query-Composable innerhalb eines effectScope ausgeführt wird. Andernfalls kann dies zu Speicherlecks führen. Wir haben dev-only-Warnungen hinzugefügt, um Benutzer über mögliche Fehlbenutzungen zu informieren.