人人都会AI编程

14.2 TanStack Query(Vue Query)

更新时间:2026-07-10

TanStack Query(原 React Query)是目前 React 生态中处理服务端状态的主流方案。它把“从服务器获取数据、缓存、同步、更新”这一整套流程封装成简单的 Hooks,让开发者不再需要手写大量的 loading、error、数据刷新逻辑。

它的核心概念主要是三个:Query(查询) 用于获取数据,Mutation(变更) 用于创建/更新/删除数据,QueryClient 是全局管理者,负责缓存、配置和主动操作。

全局配置与 QueryClientProvider

在使用之前,需要在应用根部注入 QueryClient,它是整个缓存系统的中枢。

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

// 创建实例,可以在这里配置全局默认选项
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 2,               // 失败重试次数
      staleTime: 1000 * 60,   // 数据新鲜时间(默认0,即立即重新获取)
      cacheTime: 1000 * 60 * 5, // 缓存保留时间(v5 中已重命名为 gcTime)
    },
  },
});

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      {/* 应用组件 */}
    </QueryClientProvider>
  );
}

数据查询:useQuery

useQuery 是获取数据的主要 Hook。你需要提供一个唯一的键queryKey)和一个获取函数queryFn),它会自动返回 dataisLoadingerror 等状态。

import { useQuery } from '@tanstack/react-query';
import axios from 'axios';

function UserProfile({ userId }) {
  const { data, isLoading, error } = useQuery({
    queryKey: ['user', userId],  // 根据 userId 缓存不同数据
    queryFn: () => axios.get(`/api/users/${userId}`).then(res => res.data),
  });

  if (isLoading) return <div>加载中...</div>;
  if (error) return <div>出错了: {error.message}</div>;

  return (
    <div>
      <h2>{data.name}</h2>
      <p>{data.email}</p>
    </div>
  );
}

关键状态字段:

  • isLoading: 首次加载,无缓存数据。
  • isFetching: 正在请求(后台刷新时也可能为 true)。
  • isError: 请求出错。
  • error: 错误对象。
  • data: 成功获取的数据。

常用选项:

  • staleTime: 数据在此时间内被视为“新鲜”,不会重新请求(默认 0)。
  • cacheTime(v5 中已改为 gcTime):缓存的无用数据保留时间。
  • retry: 失败重试次数。
  • enabled: 是否自动执行查询(例如,等到某个条件满足再请求)。
  • refetchOnWindowFocus: 窗口获得焦点时是否重新获取(默认 true)。

手动刷新与重新获取

useQuery 返回的 refetch 函数可以手动触发重新查询,常用于刷新按钮。

const { data, refetch, isFetching } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
});

<button onClick={() => refetch()} disabled={isFetching}>
  刷新
</button>

数据变更:useMutation

useMutation 用于执行创建、更新、删除操作。它不像 useQuery 那样自动触发,需要你手动调用 mutate 方法。

import { useMutation, useQueryClient } from '@tanstack/react-query';

function AddTodo() {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: (newTodo) => axios.post('/api/todos', newTodo),
    onSuccess: () => {
      // 新增成功后,让列表查询失效,触发重新获取最新数据
      queryClient.invalidateQueries({ queryKey: ['todos'] });
    },
  });

  return (
    <button onClick={() => mutation.mutate({ title: '新任务' })}>
      {mutation.isLoading ? '添加中...' : '添加任务'}
    </button>
  );
}

常用回调:

  • onSuccess: 成功时执行。
  • onError: 错误时执行。
  • onSettled: 无论成功或失败都会执行。

状态字段:

  • isLoading:请求进行中。
  • isError / isSuccess:相应状态。
  • error:错误对象。

乐观更新(Optimistic Updates)

为了提升交互体验,可以在服务器响应前先更新 UI,如果请求失败则回滚。

const mutation = useMutation({
  mutationFn: updateTodo,
  onMutate: async (updatedTodo) => {
    // 取消正在进行的相关查询,防止它们覆盖乐观更新
    await queryClient.cancelQueries({ queryKey: ['todos'] });
    // 保存之前的缓存数据
    const previousTodos = queryClient.getQueryData(['todos']);
    // 乐观更新缓存
    queryClient.setQueryData(['todos'], (old) =>
      old.map(todo => todo.id === updatedTodo.id ? updatedTodo : todo)
    );
    // 返回回滚所需的快照
    return { previousTodos };
  },
  onError: (err, updatedTodo, context) => {
    // 回滚到之前的缓存
    queryClient.setQueryData(['todos'], context.previousTodos);
  },
  onSettled: () => {
    // 重新获取列表,确保数据与服务器一致
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  },
});

QueryClient 的常用方法

除了通过 Provider 提供全局实例,你也可以用 useQueryClient 获取它实例。

const queryClient = useQueryClient();

// 手动设置/更新某个查询的缓存数据
queryClient.setQueryData(['user', userId], newData);

// 使其失效,触发重新获取
queryClient.invalidateQueries({ queryKey: ['todos'] });

// 获取当前缓存数据(不发起请求)
const cachedData = queryClient.getQueryData(['todos']);

// 预取数据(如鼠标悬停时提前加载)
queryClient.prefetchQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
});

// 清除缓存
queryClient.removeQueries({ queryKey: ['todos'] });

配合分页与无限滚动

分页查询通常将页码放在 queryKey 中,自动切换:

const [page, setPage] = useState(1);
const { data, isLoading } = useQuery({
  queryKey: ['projects', page],
  queryFn: () => fetchProjects(page),
  keepPreviousData: true, // 切换页码时保留旧数据,减少闪烁
});

无限滚动使用 useInfiniteQuery

const {
  data,
  fetchNextPage,
  hasNextPage,
  isFetchingNextPage,
} = useInfiniteQuery({
  queryKey: ['projects'],
  queryFn: ({ pageParam = 1 }) => fetchProjects(pageParam),
  getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
});

// 在滚动监听中调用 fetchNextPage

实际开发中的最佳实践

  • queryKey 设计:从通用到具体,如 ['todos', 'list', { filter }],确保唯一性和可预测性。
  • 尽量使用全局配置:staleTime、retry 等不要在每个 useQuery 中重复设置。
  • 避免在 queryFn 中写副作用,它仅用于获取数据。
  • 使用 Devtools@tanstack/react-query-devtools 可以在开发时直观查看缓存状态,调试极其方便。
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

<QueryClientProvider client={queryClient}>
  {/* 你的应用 */}
  <ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>

TanStack Query 通过强大的缓存管理和请求自动化,能显著减少样板代码,提升应用数据层的健壮性。掌握好 useQueryuseMutationqueryClient 这三个核心,你就已经能应对 80% 的数据请求场景了。