前端
PythonJava运维数据库
TanStack Query
TanStack Query
  • getting started
    • 快速开始
    • 开发工具
    • Typescript
    • 默认配置
    • 网络模式
  • Query
    • 查询
    • 查询键
    • 查询函数
    • 查询选项
    • 并行查询
    • 有依赖的查询
    • 查询状态指示器
    • 窗口聚焦重新获取数据
    • 禁用/暂停查询
    • 查询重试
    • 分页查询
    • 无限查询
    • 初始化查询数据
  • 占位符
  • Mutation
    • 修改 Mutations
    • 主动查询失效 Query Invalidation
    • 修改导致的失效 Invalidation From Mutations
    • 通过修改的数据更新查询内容
    • 乐观更新
  • 取消查询
  • 默认查询函数
  • 过滤器
  • Suspense
  • 缓存数据
  • Api Reference
    • QueryClient
    • QueryCache
    • MutationCache
    • QueryObserver QueriesObserver
    • InfiniteQueryObserver
    • FocusManager
    • NotifyManager
    • useQuery
    • useQueries useInfiniteQuery
    • useMutation
    • useIsFetching
    • useIsMutating
    • useMutationState
    • useSuspenseQuery
    • useSuspenseInfiniteQuery
    • useSuspenseQueries
    • QueryClientProvider
    • useQueryClient
    • queryOptions
    • infiniteQueryOptions
    • QueryErrorResetBoundary
    • useQueryErrorResetBoundary
    • hydration
由 GitBook 提供支持
在本页
  • 方式 1:通过 UI 直接更新
  • 方式 2:直接更新缓存
  • 如何选择合适的方式?

这有帮助吗?

  1. Mutation

乐观更新

React Query 提供了两种方式在 mutation 完成之前提前更新 UI:

  1. 通过 UI 直接更新(适用于 mutation 和查询在同一个组件中)。

  2. 通过 onMutate 直接更新缓存(适用于多个组件共享数据)。

方式 1:通过 UI 直接更新

基本示例

const { isPending, variables, mutate, isError } = useMutation({
  mutationFn: (newTodo: string) => axios.post("/api/data", { text: newTodo }),
  onSettled: async () => {
    return await queryClient.invalidateQueries({ queryKey: ["todos"] });
  },
});

function App() {
  return (
    <ul>
      {todoQuery.items.map((todo) => (
        <li key={todo.id}>{todo.text}</li>
      ))}
      {isPending && <li style={{ opacity: 0.5 }}>{variables}</li>}
    </ul>
  );
}

思路

  • isPending 为 true 时,UI 先显示一个半透明的临时 todo。

  • mutation 成功后,queryClient.invalidateQueries 触发查询刷新,新的 todo 变为正常状态。

  • mutation 失败时,临时 todo 自动消失(或者你可以提供一个重试按钮)。

处理失败回滚

如果 mutation 失败,可以使用 isError 提供重试选项:

{
  isError && (
    <li style={{ color: "red" }}>
      {variables}
      <button onClick={() => mutate(variables)}>Retry</button>
    </li>
  );
}

方式 2:直接更新缓存

如果多个组件依赖同一个数据,UI 方式不足够,需要直接更新 queryClient。

添加 todo 的乐观更新

const queryClient = useQueryClient();

useMutation({
  mutationFn: addTodo,
  onMutate: async (newTodo) => {
    // 取消当前正在进行的查询,避免被新的请求覆盖
    await queryClient.cancelQueries({ queryKey: ["todos"] });

    // 备份当前数据(快照)
    const previousTodos = queryClient.getQueryData(["todos"]);

    // 直接更新 UI,先插入新 todo
    queryClient.setQueryData(["todos"], (old) => [...old, newTodo]);

    // 返回回滚数据
    return { previousTodos };
  },
  // 失败时回滚
  onError: (err, newTodo, context) => {
    queryClient.setQueryData(["todos"], context.previousTodos);
  },
  // 成功或失败后都重新拉取数据
  onSettled: () => {
    queryClient.invalidateQueries({ queryKey: ["todos"] });
  },
});

思路

  1. onMutate

    • 先取消正在进行的 todos 请求。

    • 备份原始 todos 数据,防止 mutation 失败。

    • 立即插入新 todo,提高 UI 响应速度。

  2. onError

    • 如果 mutation 失败,则回滚 todos,恢复原始状态。

  3. onSettled

    • 成功或失败后,都重新拉取 todos 数据,确保和服务器数据一致。

更新单个 todo 的乐观更新

useMutation({
  mutationFn: updateTodo,
  onMutate: async (newTodo) => {
    await queryClient.cancelQueries({ queryKey: ["todos", newTodo.id] });

    const previousTodo = queryClient.getQueryData(["todos", newTodo.id]);

    queryClient.setQueryData(["todos", newTodo.id], newTodo);

    return { previousTodo, newTodo };
  },
  onError: (err, newTodo, context) => {
    queryClient.setQueryData(["todos", context.newTodo.id], context.previousTodo);
  },
  onSettled: (newTodo) => {
    queryClient.invalidateQueries({ queryKey: ["todos", newTodo.id] });
  },
});

💡 思路

  • 直接更新某个 todo,mutation 成功后不会重新获取数据,提高性能。

  • 失败时回滚数据,防止 UI 变成不一致状态。


如何选择合适的方式?

方案
适用场景
优势
缺点

通过 UI 直接更新

只在当前组件显示临时数据

简单,代码量少

不能同步多个组件的数据

通过缓存 onMutate 更新

需要多个组件同步 UI

无需 refetch,UI 立即响应

需要手动管理数据回滚

一般来说:

  • UI 更新方式适用于局部 UI 交互,比如提交表单。

  • 缓存更新方式适用于全局状态,如 todos 被多个组件共享时。

上一页通过修改的数据更新查询内容下一页取消查询

最后更新于2个月前

这有帮助吗?