Skip to content
useMutation 详解

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(写入)