与Queries
不同,Mutations
主要用于创建、更新或删除数据,或者执行服务器端的副作用操作。为了支持这一点,TanStack Query 提供了 useMutation
钩子。
🌰 示例:添加一个 Todo:
function App() {
const mutation = useMutation({
mutationFn: (newTodo) => axios.post("/todos", newTodo),
});
return (
<div>
{mutation.isPending ? (
"正在添加 Todo..."
) : (
<>
{mutation.isError ? <div>发生错误: {mutation.error.message}</div> : null}
{mutation.isSuccess ? <div>Todo 添加成功!</div> : null}
<button
onClick={() => {
mutation.mutate({ id: new Date(), title: "洗衣服" });
}}
>
创建 Todo
</button>
</>
)}
</div>
);
}
mutation
在任何时候都只会处于以下状态之一:
isIdle
或 status === 'idle'
:Mutation
处于空闲状态,或者已重置。
isPending
或 status === 'pending'
:Mutation
正在执行中。
isError
或 status === 'error'
:Mutation
发生了错误。
isSuccess
或 status === 'success'
: Mutation
成功完成,数据已可用。
除此之外,还有一些额外信息可用:
error
:当Mutation
失败时,可以通过error
获取错误信息。
data
:当Mutation
成功时,可以通过data
获取返回的数据。
在上面的示例中,你可以看到mutate
方法接受参数,并将其传递给mutationFn
进行处理。
Mutation 的增强功能
在事件处理函数中调用 mutate
在 React 16 及更早版本中,mutate
不能直接在事件回调函数中使用,因为 React 事件会被回收。可以通过额外包装一层来解决:
// ❌ 这样在 React 16 及更早版本会报错
const CreateTodo = () => {
const mutation = useMutation({
mutationFn: (event) => {
event.preventDefault();
return fetch("/api", new FormData(event.target));
},
});
return <form onSubmit={mutation.mutate}>...</form>;
};
// ✅ 正确的写法
const CreateTodo = () => {
const mutation = useMutation({
mutationFn: (formData) => fetch("/api", formData);
});
const onSubmit = (event) => {
event.preventDefault();
mutation.mutate(new FormData(event.target));
};
return <form onSubmit={onSubmit}>...</form>;
};
重置 Mutation 状态
如果需要清除 Mutation
产生的 error
或 data
,可以使用 reset
方法:
const CreateTodo = () => {
const [title, setTitle] = useState("");
const mutation = useMutation({ mutationFn: createTodo });
const onCreateTodo = (e) => {
e.preventDefault();
mutation.mutate({ title });
};
return (
<form onSubmit={onCreateTodo}>
{mutation.error && <h5 onClick={() => mutation.reset()}>{mutation.error}</h5>}
<input type="text" value={title} onChange={(e) => setTitle(e.target.value)} />
<br />
<button type="submit">创建 Todo</button>
</form>
);
};
Mutation 的生命周期回调
useMutation
提供了多个回调,可以在 Mutation
的不同阶段执行副作用(如更新 UI、日志记录等):
useMutation({
mutationFn: addTodo,
onMutate: (variables) => {
// Mutation 即将开始!
// 可以返回一个上下文(context),用于回滚
return { id: 1 };
},
onError: (error, variables, context) => {
console.log(`回滚操作,撤销 ID 为 ${context.id} 的更新`);
},
onSuccess: (data, variables, context) => {
console.log("Mutation 成功!");
},
onSettled: (data, error, variables, context) => {
console.log("无论成功还是失败,都会执行");
},
});
这些回调可以返回 Promise
,下一个回调会等待前一个回调完成后执行:
useMutation({
mutationFn: addTodo,
onSuccess: async () => {
console.log("我是第一个执行的");
},
onSettled: async () => {
console.log("我是第二个执行的");
},
});
传递额外的回调
在调用 mutate
时,可以额外指定 onSuccess
、onError
、onSettled
,这些回调会在 Mutation 完成后执行:
mutate(todo, {
onSuccess: (data, variables, context) => {
console.log("这是额外的 onSuccess 处理");
},
onError: (error, variables, context) => {
console.log("这是额外的 onError 处理");
},
onSettled: (data, error, variables, context) => {
console.log("这是额外的 onSettled 处理");
},
});
Mutation 并发与顺序执行
默认情况下,所有 Mutation
是并行执行的。如果希望某些 Mutation
按顺序执行,可以使用 scope
,拥有相同 scope.id
的 Mutation
将按照顺序执行。
const mutation = useMutation({
mutationFn: addTodo,
scope: {
id: "todo",
},
});
使用 mutateAsync 获取 Promise
如果需要在代码中以 await
方式使用 Mutation
,可以使用 mutateAsync
:
const mutation = useMutation({ mutationFn: addTodo });
try {
const todo = await mutation.mutateAsync(todo);
console.log(todo);
} catch (error) {
console.error(error);
} finally {
console.log("操作完成");
}
失败时自动重试
默认情况下,Mutation
失败后不会重试。但可以通过 retry
选项开启重试机制:
useMutation({
mutationFn: addTodo,
retry: 3, // 失败后最多重试 3 次
});
当设备离线导致 Mutation
失败时,会在设备恢复在线时自动重试。
Mutation 状态的持久化
Mutation
可以持久化存储,以便在应用重启后恢复未完成的 Mutation
:
const queryClient = new QueryClient();
queryClient.setMutationDefaults(["addTodo"], {
mutationFn: addTodo,
retry: 3,
});
// 持久化 Mutation 状态
const state = dehydrate(queryClient);
hydrate(queryClient, state);
// 重新恢复暂停的 Mutations
queryClient.resumePausedMutations();