Im Gegensatz zu Queries werden Mutationen typischerweise zum Erstellen/Aktualisieren/Löschen von Daten oder zum Ausführen von serverseitigen Effekten verwendet. Zu diesem Zweck exportiert TanStack Query den Hook useMutation.
Hier ist ein Beispiel für eine Mutation, die einen neuen Todo-Eintrag auf dem Server hinzufügt
<script setup>
import { useMutation } from '@tanstack/vue-query'
const { isPending, isError, error, isSuccess, mutate } = useMutation({
mutationFn: (newTodo) => axios.post('/todos', newTodo),
})
function addTodo() {
mutate({ id: new Date(), title: 'Do Laundry' })
}
</script>
<template>
<span v-if="isPending">Adding todo...</span>
<span v-else-if="isError">An error occurred: {{ error.message }}</span>
<span v-else-if="isSuccess">Todo added!</span>
<button @click="addTodo">Create Todo</button>
</template>
<script setup>
import { useMutation } from '@tanstack/vue-query'
const { isPending, isError, error, isSuccess, mutate } = useMutation({
mutationFn: (newTodo) => axios.post('/todos', newTodo),
})
function addTodo() {
mutate({ id: new Date(), title: 'Do Laundry' })
}
</script>
<template>
<span v-if="isPending">Adding todo...</span>
<span v-else-if="isError">An error occurred: {{ error.message }}</span>
<span v-else-if="isSuccess">Todo added!</span>
<button @click="addTodo">Create Todo</button>
</template>
Eine Mutation kann sich zu jedem gegebenen Zeitpunkt nur in einem der folgenden Zustände befinden
Über diese Hauptzustände hinaus sind je nach Zustand der Mutation weitere Informationen verfügbar
Im obigen Beispiel haben Sie auch gesehen, dass Sie Variablen an Ihre Mutationsfunktion übergeben können, indem Sie die Funktion mutate mit **einer einzelnen Variablen oder einem Objekt** aufrufen.
Auch mit nur Variablen sind Mutationen nicht so besonders, aber wenn sie mit der Option onSuccess, der Methode invalidateQueries des Query Clients und der Methode setQueryData des Query Clients verwendet werden, werden Mutationen zu einem sehr mächtigen Werkzeug.
Manchmal müssen Sie den error oder die data einer Mutationsanfrage löschen. Dazu können Sie die Funktion reset verwenden.
<script>
import { useMutation } from '@tanstack/vue-query'
const { error, mutate, reset } = useMutation({
mutationFn: (newTodo) => axios.post('/todos', newTodo),
})
function addTodo() {
mutate({ id: new Date(), title: 'Do Laundry' })
}
</script>
<template>
<span v-else-if="error">
<span>An error occurred: {{ error.message }}</span>
<button @click="reset">Reset error</button>
</span>
<button @click="addTodo">Create Todo</button>
</template>
<script>
import { useMutation } from '@tanstack/vue-query'
const { error, mutate, reset } = useMutation({
mutationFn: (newTodo) => axios.post('/todos', newTodo),
})
function addTodo() {
mutate({ id: new Date(), title: 'Do Laundry' })
}
</script>
<template>
<span v-else-if="error">
<span>An error occurred: {{ error.message }}</span>
<button @click="reset">Reset error</button>
</span>
<button @click="addTodo">Create Todo</button>
</template>
useMutation bietet einige Hilfsoptionen, die schnelle und einfache Nebeneffekte in jeder Phase des Mutationslebenszyklus ermöglichen. Diese sind nützlich für das Invalidieren und erneute Abrufen von Queries nach Mutationen und sogar für optimistische Updates.
useMutation({
mutationFn: addTodo,
onMutate: (variables) => {
// A mutation is about to happen!
// Optionally return a context containing data to use when for example rolling back
return { id: 1 }
},
onError: (error, variables, context) => {
// An error happened!
console.log(`rolling back optimistic update with id ${context.id}`)
},
onSuccess: (data, variables, context) => {
// Boom baby!
},
onSettled: (data, error, variables, context) => {
// Error or success... doesn't matter!
},
})
useMutation({
mutationFn: addTodo,
onMutate: (variables) => {
// A mutation is about to happen!
// Optionally return a context containing data to use when for example rolling back
return { id: 1 }
},
onError: (error, variables, context) => {
// An error happened!
console.log(`rolling back optimistic update with id ${context.id}`)
},
onSuccess: (data, variables, context) => {
// Boom baby!
},
onSettled: (data, error, variables, context) => {
// Error or success... doesn't matter!
},
})
Wenn Sie in einer der Callback-Funktionen ein Promise zurückgeben, wird dieses zuerst abgewartet, bevor der nächste Callback aufgerufen wird.
useMutation({
mutationFn: addTodo,
onSuccess: async () => {
console.log("I'm first!")
},
onSettled: async () => {
console.log("I'm second!")
},
})
useMutation({
mutationFn: addTodo,
onSuccess: async () => {
console.log("I'm first!")
},
onSettled: async () => {
console.log("I'm second!")
},
})
Möglicherweise möchten Sie zusätzliche Rückrufe auslösen, die über die auf useMutation definierten hinausgehen, wenn Sie mutate aufrufen. Dies kann verwendet werden, um komponentenspezifische Nebeneffekte auszulösen. Dazu können Sie beliebige der gleichen Callback-Optionen an die Funktion mutate übergeben, nachdem Ihre Mutationsvariable. Unterstützte Optionen sind: onSuccess, onError und onSettled. Bitte beachten Sie, dass diese zusätzlichen Rückrufe nicht ausgeführt werden, wenn Ihre Komponente unmountet wird, bevor die Mutation abgeschlossen ist.
useMutation({
mutationFn: addTodo,
onSuccess: (data, variables, context) => {
// I will fire first
},
onError: (error, variables, context) => {
// I will fire first
},
onSettled: (data, error, variables, context) => {
// I will fire first
},
})
mutate(todo, {
onSuccess: (data, variables, context) => {
// I will fire second!
},
onError: (error, variables, context) => {
// I will fire second!
},
onSettled: (data, error, variables, context) => {
// I will fire second!
},
})
useMutation({
mutationFn: addTodo,
onSuccess: (data, variables, context) => {
// I will fire first
},
onError: (error, variables, context) => {
// I will fire first
},
onSettled: (data, error, variables, context) => {
// I will fire first
},
})
mutate(todo, {
onSuccess: (data, variables, context) => {
// I will fire second!
},
onError: (error, variables, context) => {
// I will fire second!
},
onSettled: (data, error, variables, context) => {
// I will fire second!
},
})
Es gibt einen geringfügigen Unterschied in der Handhabung von onSuccess, onError und onSettled Rückrufen bei aufeinanderfolgenden Mutationen. Wenn sie an die Funktion mutate übergeben werden, werden sie nur einmal und nur dann ausgelöst, wenn die Komponente noch gemountet ist. Dies liegt daran, dass der Mutationsbeobachter bei jedem Aufruf der Funktion mutate entfernt und neu abonniert wird. Im Gegensatz dazu werden die Handler von useMutation für jeden Aufruf von mutate ausgeführt.
Beachten Sie, dass die an useMutation übergebene mutationFn höchstwahrscheinlich asynchron ist. In diesem Fall kann die Reihenfolge, in der Mutationen erfüllt werden, von der Reihenfolge der Aufrufe der Funktion mutate abweichen.
useMutation({
mutationFn: addTodo,
onSuccess: (data, variables, context) => {
// Will be called 3 times
},
})
const todos = ['Todo 1', 'Todo 2', 'Todo 3']
todos.forEach((todo) => {
mutate(todo, {
onSuccess: (data, variables, context) => {
// Will execute only once, for the last mutation (Todo 3),
// regardless which mutation resolves first
},
})
})
useMutation({
mutationFn: addTodo,
onSuccess: (data, variables, context) => {
// Will be called 3 times
},
})
const todos = ['Todo 1', 'Todo 2', 'Todo 3']
todos.forEach((todo) => {
mutate(todo, {
onSuccess: (data, variables, context) => {
// Will execute only once, for the last mutation (Todo 3),
// regardless which mutation resolves first
},
})
})
Verwenden Sie mutateAsync anstelle von mutate, um ein Promise zu erhalten, das bei Erfolg aufgelöst oder bei einem Fehler einen Fehler auslöst. Dies kann beispielsweise zur Zusammensetzung von Seiteneffekten verwendet werden.
const mutation = useMutation({ mutationFn: addTodo })
try {
const todo = await mutation.mutateAsync(todo)
console.log(todo)
} catch (error) {
console.error(error)
} finally {
console.log('done')
}
const mutation = useMutation({ mutationFn: addTodo })
try {
const todo = await mutation.mutateAsync(todo)
console.log(todo)
} catch (error) {
console.error(error)
} finally {
console.log('done')
}
Standardmäßig versucht TanStack Query nicht, eine Mutation bei einem Fehler erneut auszuführen. Mit der Option retry ist dies jedoch möglich.
const mutation = useMutation({
mutationFn: addTodo,
retry: 3,
})
const mutation = useMutation({
mutationFn: addTodo,
retry: 3,
})
Wenn Mutationen fehlschlagen, weil das Gerät offline ist, werden sie in derselben Reihenfolge wiederholt, wenn das Gerät wieder online geht.
Mutationen können bei Bedarf in einem Speicher persistiert und zu einem späteren Zeitpunkt wieder aufgenommen werden. Dies kann mit den Hydrationsfunktionen erfolgen.
const queryClient = new QueryClient()
// Define the "addTodo" mutation
queryClient.setMutationDefaults(['addTodo'], {
mutationFn: addTodo,
onMutate: async (variables) => {
// Cancel current queries for the todos list
await queryClient.cancelQueries({ queryKey: ['todos'] })
// Create optimistic todo
const optimisticTodo = { id: uuid(), title: variables.title }
// Add optimistic todo to todos list
queryClient.setQueryData(['todos'], (old) => [...old, optimisticTodo])
// Return context with the optimistic todo
return { optimisticTodo }
},
onSuccess: (result, variables, context) => {
// Replace optimistic todo in the todos list with the result
queryClient.setQueryData(['todos'], (old) =>
old.map((todo) =>
todo.id === context.optimisticTodo.id ? result : todo,
),
)
},
onError: (error, variables, context) => {
// Remove optimistic todo from the todos list
queryClient.setQueryData(['todos'], (old) =>
old.filter((todo) => todo.id !== context.optimisticTodo.id),
)
},
retry: 3,
})
// Start mutation in some component:
const mutation = useMutation({ mutationKey: ['addTodo'] })
mutation.mutate({ title: 'title' })
// If the mutation has been paused because the device is for example offline,
// Then the paused mutation can be dehydrated when the application quits:
const state = dehydrate(queryClient)
// The mutation can then be hydrated again when the application is started:
hydrate(queryClient, state)
// Resume the paused mutations:
queryClient.resumePausedMutations()
const queryClient = new QueryClient()
// Define the "addTodo" mutation
queryClient.setMutationDefaults(['addTodo'], {
mutationFn: addTodo,
onMutate: async (variables) => {
// Cancel current queries for the todos list
await queryClient.cancelQueries({ queryKey: ['todos'] })
// Create optimistic todo
const optimisticTodo = { id: uuid(), title: variables.title }
// Add optimistic todo to todos list
queryClient.setQueryData(['todos'], (old) => [...old, optimisticTodo])
// Return context with the optimistic todo
return { optimisticTodo }
},
onSuccess: (result, variables, context) => {
// Replace optimistic todo in the todos list with the result
queryClient.setQueryData(['todos'], (old) =>
old.map((todo) =>
todo.id === context.optimisticTodo.id ? result : todo,
),
)
},
onError: (error, variables, context) => {
// Remove optimistic todo from the todos list
queryClient.setQueryData(['todos'], (old) =>
old.filter((todo) => todo.id !== context.optimisticTodo.id),
)
},
retry: 3,
})
// Start mutation in some component:
const mutation = useMutation({ mutationKey: ['addTodo'] })
mutation.mutate({ title: 'title' })
// If the mutation has been paused because the device is for example offline,
// Then the paused mutation can be dehydrated when the application quits:
const state = dehydrate(queryClient)
// The mutation can then be hydrated again when the application is started:
hydrate(queryClient, state)
// Resume the paused mutations:
queryClient.resumePausedMutations()
Wenn Sie Offline-Mutationen mit dem persistQueryClient Plugin speichern, können Mutationen beim Neuladen der Seite nicht fortgesetzt werden, es sei denn, Sie geben eine Standard-Mutationsfunktion an.
Dies ist eine technische Einschränkung. Wenn in einen externen Speicher persistiert wird, wird nur der Zustand von Mutationen persistiert, da Funktionen nicht serialisiert werden können. Nach der Hydration ist die Komponente, die die Mutation auslöst, möglicherweise nicht gemountet, sodass der Aufruf von resumePausedMutations einen Fehler zurückgeben kann: No mutationFn found.
const client = new QueryClient({
defaultOptions: {
queries: {
gcTime: 1000 * 60 * 60 * 24, // 24 hours
},
},
})
// we need a default mutation function so that paused mutations can resume after a page reload
queryClient.setMutationDefaults({
mutationKey: ['todos'],
mutationFn: ({ id, data }) => {
return api.updateTodo(id, data)
},
})
const vueQueryOptions: VueQueryPluginOptions = {
queryClient: client,
clientPersister: (queryClient) => {
return persistQueryClient({
queryClient,
persister: createAsyncStoragePersister({ storage: localStorage }),
})
},
clientPersisterOnSuccess: (queryClient) => {
queryClient.resumePausedMutations()
},
}
createApp(App).use(VueQueryPlugin, vueQueryOptions).mount('#app')
const client = new QueryClient({
defaultOptions: {
queries: {
gcTime: 1000 * 60 * 60 * 24, // 24 hours
},
},
})
// we need a default mutation function so that paused mutations can resume after a page reload
queryClient.setMutationDefaults({
mutationKey: ['todos'],
mutationFn: ({ id, data }) => {
return api.updateTodo(id, data)
},
})
const vueQueryOptions: VueQueryPluginOptions = {
queryClient: client,
clientPersister: (queryClient) => {
return persistQueryClient({
queryClient,
persister: createAsyncStoragePersister({ storage: localStorage }),
})
},
clientPersisterOnSuccess: (queryClient) => {
queryClient.resumePausedMutations()
},
}
createApp(App).use(VueQueryPlugin, vueQueryOptions).mount('#app')
Wir haben auch ein ausführliches Offline-Beispiel, das sowohl Queries als auch Mutationen abdeckt.
Standardmäßig laufen alle Mutationen parallel – auch wenn Sie .mutate() derselben Mutation mehrmals aufrufen. Mutationen kann ein scope mit einer id zugewiesen werden, um dies zu vermeiden. Alle Mutationen mit derselben scope.id werden seriell ausgeführt, was bedeutet, dass sie beim Auslösen in einem Zustand isPaused: true beginnen, wenn bereits eine Mutation für diesen Bereich aktiv ist. Sie werden in eine Warteschlange gestellt und automatisch fortgesetzt, sobald ihre Zeit in der Warteschlange gekommen ist.
const mutation = useMutation({
mutationFn: addTodo,
scope: {
id: 'todo',
},
})
const mutation = useMutation({
mutationFn: addTodo,
scope: {
id: 'todo',
},
})