useMutation 详解
useMutation 用于创建、更新和删除数据。与 useQuery 不同:Mutation 不缓存、不自动执行、不与查询键关联——你需要主动调用 mutate() 触发它。
完整签名
const mutation = useMutation({
mutationFn: (variables) => apiCall(variables), // 必填
// 回调
onMutate: async (variables) => { /* 乐观更新 */ },
onSuccess: (data, variables, context) => { /* 成功处理 */ },
onError: (error, variables, context) => { /* 错误回滚 */ },
onSettled: (data, error, variables, context) => { /* 无论成败 */ },
// 配置
retry: 0,
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
networkMode: 'online',
throwOnError: false,
mutationKey: undefined,
meta: undefined,
})
// 触发
mutation.mutate(variables, { onSuccess, onError, onSettled })
mutation.mutateAsync(variables) // 返回 Promise
返回值
const {
mutate, // (variables, options?) => void
mutateAsync, // (variables, options?) => Promise<TData>
reset, // () => void — 重置 mutation 状态
// 状态
isPending, // 正在执行
isIdle, // 尚未触发
isError, // 执行失败
isSuccess, // 执行成功
isPaused, // 网络离线暂停
status, // 'idle' | 'pending' | 'error' | 'success'
// 数据
data, // 最后一次成功返回的数据
error, // 错误对象
variables, // 传给 mutate 的变量
failureCount, // 失败次数
failureReason, // 失败原因
submittedAt, // 提交时间戳
} = useMutation({ mutationFn: createTodo })mutate vs mutateAsync
| 方法 | 返回 | 用途 |
|---|---|---|
mutate(variables, callbacks?) |
void |
主流选择:fire-and-forget + 回调处理 |
mutateAsync(variables, callbacks?) |
Promise<TData> |
需要 await 结果时(Formik onSubmit、链式调用) |
// ✅ mutate:常规用法
const createTodo = useMutation({
mutationFn: postTodo,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
})
createTodo.mutate({ title: 'Buy milk' })
// ✅ mutateAsync:需要获取返回值
async function handleSubmit(data: FormData) {
try {
const result = await createTodo.mutateAsync(data)
toast.success(`创建成功: ${result.id}`)
} catch (error) {
toast.error('创建失败')
}
}🚨 陷阱:
mutate不会返回 Promise,不能用await。如果mutationFn抛出异常,mutate不会让外层try/catch捕获——异常通过onError回调或mutation.isError处理。
回调执行顺序
mutate(variables)
│
▼
onMutate(variables) ← 异步执行,可返回 context
│
▼
mutationFn(variables) ← 实际的网络请求
│
├── 成功 ──→ onSuccess(data, variables, context)
│ │
│ └──→ onSettled(data, null, variables, context)
│
└── 失败 ──→ onError(error, variables, context)
│
└──→ onSettled(undefined, error, variables, context)回调参数说明
useMutation({
mutationFn: (newTodo: CreateTodoInput) => api.createTodo(newTodo),
onMutate: async (variables: CreateTodoInput) => {
// variables = 传给 mutate 的参数
// 返回值成为 onSuccess / onError 的第三个参数(context)
return { previousTodos }
},
onSuccess: (
data: Todo, // mutationFn 的返回值
variables: CreateTodoInput, // 传给 mutate 的参数
context: { previousTodos } // onMutate 的返回值
) => { /* ... */ },
onError: (
error: Error,
variables: CreateTodoInput,
context: { previousTodos } | undefined // onMutate 失败时可能不存在
) => { /* ... */ },
onSettled: (
data: Todo | undefined, // 成功时有值
error: Error | null, // 失败时有值
variables: CreateTodoInput,
context: { previousTodos } | undefined
) => { /* ... */ },
})reset — 重置状态
const mutation = useMutation({ mutationFn: createTodo })
mutation.mutate({ title: 'Test' })
// ... mutation.isSuccess === true
mutation.reset()
// ... 现在 mutation.isIdle === true, data/error 全部清空
适用场景:表单提交后将 mutation 状态重置,以便再次提交。
并发 Mutation 控制
useMutation 本身不限制并发。如需排队执行,使用 mutateAsync + 手动管理:
const mutation = useMutation({ mutationFn: createTodo })
// ❌ 可能竞态:两次快速点击
<button onClick={() => mutation.mutate({ title: 'A' })}>添加A</button>
<button onClick={() => mutation.mutate({ title: 'B' })}>添加B</button>
// ✅ 串行执行
async function handleAdd(title: string) {
await mutation.mutateAsync({ title }) // 等第一个完成再发送第二个
}💡 最佳实践:对列表批量操作,用
useMutation配合Promise.all或 for-await 循环。对于独立 mutation,多个useMutation实例各自管理自己的状态更清晰。
mutationKey — 标识与去重
v5 支持为 mutation 设置 mutationKey,主要用于 DevTools 分组和全局回调过滤:
useMutation({
mutationKey: ['todo', 'create'],
mutationFn: createTodo,
})
// QueryCache / MutationCache 全局回调中可按 key 过滤
const queryClient = new QueryClient({
mutationCache: new MutationCache({
onSuccess: (data, variables, context, mutation) => {
if (mutation.options.mutationKey?.[0] === 'todo') {
// 处理所有 todo 相关 mutation
}
},
}),
})全局 Mutation 回调
在 MutationCache 中注册全局回调,避免在每个 useMutation 中重复代码:
const queryClient = new QueryClient({
mutationCache: new MutationCache({
onSuccess: (data, variables, context, mutation) => {
toast.success('操作成功')
},
onError: (error, variables, context, mutation) => {
// 只对非取消操作报错
if (error.name !== 'AbortError') {
toast.error(`操作失败: ${error.message}`)
}
},
}),
})与 useQuery 的对比
| 维度 | useQuery | useMutation |
|---|---|---|
| 触发时机 | 自动(挂载/依赖变化) | 手动(调用 mutate()) |
| 缓存 | 是 | 否 |
| 共享 | 同键共享 | 不共享 |
| 重试 | 默认 3 次 | 默认 0 次 |
| 查询键 | 必填 | 可选(mutationKey) |
| isPending 含义 | 首次加载无数据 | 正在执行 |
| 主要用途 | GET(读取) | POST/PUT/PATCH/DELETE(写入) |