译零 TransZero · TanStack Query 中文文档
主站

修改

更新于 2026-11-02 React 查看英文原文 ↗

与查询不同,修改通常用于创建/更新/删除数据或执行服务端副作用(server side-effects)。为此,TanStack Query 导出了一个 useMutation Hook。

以下是一个将新待办事项添加到服务端的修改示例:

TypeScript
1function App() {2  const mutation = useMutation({3    mutationFn: (newTodo) => {4      return axios.post('/todos', newTodo)5    },6  })7 8  return (9    <div>10      {mutation.isPending ? (11        'Adding todo...'12      ) : (13        <>14          {mutation.isError ? (15            <div>An error occurred: {mutation.error.message}</div>16          ) : null}1718          {mutation.isSuccess ? <div>Todo added!</div> : null}1920          <button21            onClick={() => {22              mutation.mutate({ id: new Date(), title: 'Do Laundry' })23            }}24          >25            Create Todo26          </button>27        </>28      )}29    </div>30  )31}

在任意时刻,一个修改只能处于以下状态之一:

  • isIdle 或 status === 'idle' —— 修改当前处于空闲状态,或处于新鲜/重置状态
  • isPending 或 status === 'pending' —— 修改当前正在运行
  • isError 或 status === 'error' —— 修改遇到了错误
  • isSuccess 或 status === 'success' —— 修改成功完成,且修改数据可用

除上述主要状态外,根据修改所处的具体状态,还可获得更多信息:

  • error —— 若修改处于 error 状态,则可通过 error 属性获取错误信息。
  • data —— 若修改处于 success 状态,则可通过 data 属性获取数据。

在上面的示例中,你还看到:可通过向 mutate 函数传入单个变量或一个对象,将变量传递给修改函数。

即使仅使用变量,修改本身也并不特别;但当与 onSuccess 选项、Query Client 的 invalidateQueries 方法 和 Query Client 的 setQueryData 方法 结合使用时,修改便成为一种非常强大的工具。

重要提示:mutate 函数是一个异步函数,这意味着你无法在 React 16 及更早版本 的事件回调中直接调用它。如果你需要在 onSubmit 中访问事件对象,则必须将 mutate 包裹在另一个函数中。这是由于 React 事件池化(event pooling) 所致。

TypeScript
1// This will not work in React 16 and earlier2const CreateTodo = () => {3  const mutation = useMutation({4    mutationFn: (event) => {5      event.preventDefault()6      return fetch('/api', new FormData(event.target))7    },8  })9 10  return <form onSubmit={mutation.mutate}>...</form>11}12 13// This will work14const CreateTodo = () => {15  const mutation = useMutation({16    mutationFn: (formData) => {17      return fetch('/api', formData)18    },19  })20  const onSubmit = (event) => {21    event.preventDefault()22    mutation.mutate(new FormData(event.target))23  }24 25  return <form onSubmit={onSubmit}>...</form>26}

重置修改状态

有时你需要清除某次修改请求的 error 或 data。为此,可使用 reset 函数来处理:

TypeScript
1const CreateTodo = () => {2  const [title, setTitle] = useState('')3  const mutation = useMutation({ mutationFn: createTodo })4 5  const onCreateTodo = (e) => {6    e.preventDefault()7    mutation.mutate({ title })8  }9 10  return (11    <form onSubmit={onCreateTodo}>12      {mutation.error && (13        <h5 onClick={() => mutation.reset()}>{mutation.error}</h5>14      )}15      <input16        type="text"17        value={title}18        onChange={(e) => setTitle(e.target.value)}19      />20      <br />21      <button type="submit">Create Todo</button>22    </form>23  )24}

修改副作用

useMutation 提供了一些辅助选项,允许你在修改生命周期的任一阶段快速、便捷地执行副作用。这些选项既适用于 修改后使查询失效并重新拉取,也适用于 乐观更新。

TypeScript
1useMutation({2  mutationFn: addTodo,3  onMutate: (variables, context) => {4    // A mutation is about to happen!5 6    // Optionally return a result containing data to use when for example rolling back7    return { id: 1 }8  },9  onError: (error, variables, onMutateResult, context) => {10    // An error happened!11    console.log(`rolling back optimistic update with id ${onMutateResult.id}`)12  },13  onSuccess: (data, variables, onMutateResult, context) => {14    // Boom baby!15  },16  onSettled: (data, error, variables, onMutateResult, context) => {17    // Error or success... doesn't matter!18  },19})

若在任意回调函数中返回一个 Promise,该 Promise 将先被 await,然后才会调用下一个回调:

TypeScript
1useMutation({2  mutationFn: addTodo,3  onSuccess: async () => {4    console.log("I'm first!")5  },6  onSettled: async () => {7    console.log("I'm second!")8  },9})

你可能希望在调用 mutate 时,触发超出 useMutation 上定义的回调之外的额外回调。这可用于触发组件特定的副作用。为此,你可在修改变量之后,向 mutate 函数传入任意相同的回调选项。支持的选项包括:onSuccess、onError 和 onSettled。请注意:若组件在修改完成前卸载,这些额外回调将不会执行。

TypeScript
1useMutation({2  mutationFn: addTodo,3  onSuccess: (data, variables, onMutateResult, context) => {4    // I will fire first5  },6  onError: (error, variables, onMutateResult, context) => {7    // I will fire first8  },9  onSettled: (data, error, variables, onMutateResult, context) => {10    // I will fire first11  },12})13 14mutate(todo, {15  onSuccess: (data, variables, onMutateResult, context) => {16    // I will fire second!17  },18  onError: (error, variables, onMutateResult, context) => {19    // I will fire second!20  },21  onSettled: (data, error, variables, onMutateResult, context) => {22    // I will fire second!23  },24})

连续修改

在处理连续修改时,onSuccess、onError 和 onSettled 回调的行为略有不同。当它们作为参数传给 mutate 函数时,仅会在组件仍挂载的前提下触发一次。这是因为每次调用 mutate 时,修改观察者(mutation observer)都会被移除并重新订阅。相反,useMutation 上定义的处理器会对每次 mutate 调用都执行。

请注意:传给 useMutation 的 mutationFn 很可能是异步的。此时,修改实际完成的顺序可能与 mutate 函数调用的顺序不一致。

TypeScript
1useMutation({2  mutationFn: addTodo,3  onSuccess: (data, variables, onMutateResult, context) => {4    // Will be called 3 times5  },6})7 8const todos = ['Todo 1', 'Todo 2', 'Todo 3']9todos.forEach((todo) => {10  mutate(todo, {11    onSuccess: (data, variables, onMutateResult, context) => {12      // Will execute only once, for the last mutation (Todo 3),13      // regardless which mutation resolves first14    },15  })16})

Promise

使用 mutateAsync 替代 mutate,可获得一个 Promise:成功时解析(resolve),出错时抛出异常(throw)。例如,这可用于组合副作用。

TypeScript
1const mutation = useMutation({ mutationFn: addTodo })2 3try {4  const todo = await mutation.mutateAsync(todo)5  console.log(todo)6} catch (error) {7  console.error(error)8} finally {9  console.log('done')10}

重试

默认情况下,TanStack Query 不会在修改出错时重试,但可通过 retry 选项启用重试:

TypeScript
1const mutation = useMutation({2  mutationFn: addTodo,3  retry: 3,4})

若因设备离线导致修改失败,设备重连后,这些修改将按原有顺序重试。

持久化修改

如有需要,修改可持久化到存储中,并在后续恢复执行。这可通过注水(hydration)函数实现:

TypeScript
1const queryClient = new QueryClient()2 3// Define the "addTodo" mutation4queryClient.setMutationDefaults(['addTodo'], {5  mutationFn: addTodo,6  onMutate: async (variables, context) => {7    // Cancel current queries for the todos list8    await context.client.cancelQueries({ queryKey: ['todos'] })9 10    // Create optimistic todo11    const optimisticTodo = { id: uuid(), title: variables.title }12 13    // Add optimistic todo to todos list14    context.client.setQueryData(['todos'], (old) => [...old, optimisticTodo])15 16    // Return a result with the optimistic todo17    return { optimisticTodo }18  },19  onSuccess: (result, variables, onMutateResult, context) => {20    // Replace optimistic todo in the todos list with the result21    context.client.setQueryData(['todos'], (old) =>22      old.map((todo) =>23        todo.id === onMutateResult.optimisticTodo.id ? result : todo,24      ),25    )26  },27  onError: (error, variables, onMutateResult, context) => {28    // Remove optimistic todo from the todos list29    context.client.setQueryData(['todos'], (old) =>30      old.filter((todo) => todo.id !== onMutateResult.optimisticTodo.id),31    )32  },33  retry: 3,34})35 36// Start mutation in some component:37const mutation = useMutation({ mutationKey: ['addTodo'] })38mutation.mutate({ title: 'title' })39 40// If the mutation has been paused because the device is for example offline,41// Then the paused mutation can be dehydrated when the application quits:42const state = dehydrate(queryClient)43 44// The mutation can then be hydrated again when the application is started:45hydrate(queryClient, state)46 47// Resume the paused mutations:48queryClient.resumePausedMutations()

持久化离线修改

若使用 persistQueryClient 插件 持久化离线修改,则页面重载后无法恢复这些修改,除非你为修改提供了默认的修改函数。

这是一个技术限制。在外部存储中持久化时,仅会保存修改的状态,而函数无法被序列化。注水后,触发该修改的组件可能尚未挂载,因此调用 resumePausedMutations 可能导致错误:No mutationFn found。

TypeScript
1const persister = createAsyncStoragePersister({2  storage: window.localStorage,3})4const queryClient = new QueryClient({5  defaultOptions: {6    queries: {7      gcTime: 24 * 60 * 60 * 1000, // 24 hours8    },9  },10})11 12// we need a default mutation function so that paused mutations can resume after a page reload13queryClient.setMutationDefaults(['todos'], {14  mutationFn: ({ id, data }) => {15    return api.updateTodo(id, data)16  },17})18 19export default function App() {20  return (21    <PersistQueryClientProvider22      client={queryClient}23      persistOptions={{ persister }}24      onSuccess={() => {25        // resume mutations after initial restore from localStorage was successful26        queryClient.resumePausedMutations()27      }}28    >29      <RestOfTheApp />30    </PersistQueryClientProvider>31  )32}

我们还提供了一个详尽的 离线示例,涵盖查询和修改两方面。

修改作用域

默认情况下,所有修改均并行执行——即使你多次调用同一修改的 .mutate()。可通过为修改指定一个 scope 并配合 id 来避免这种情况。所有具有相同 scope.id 的修改将串行执行,即:当它们被触发时,若该作用域内已有修改正在运行,则新修改将进入 isPaused: true 状态;它们会被加入队列,并在其轮到执行时自动恢复。

TypeScript
1const mutation = useMutation({2  mutationFn: addTodo,3  scope: {4    id: 'todo',5  },6})

延伸阅读

如需了解更多关于修改的信息,请参阅 TkDodo 撰写的《掌握 React Query 中的修改》一文:Mastering Mutations in React Query。

本页为 TanStack Query 文档(React 版)的非官方中文翻译,原文由上游团队维护,遵循 MIT 许可。 译文如有偏差,以英文原文为准。

译自上游 59d11de