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),它会自动返回 data、isLoading、error 等状态。
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 通过强大的缓存管理和请求自动化,能显著减少样板代码,提升应用数据层的健壮性。掌握好 useQuery、useMutation 和 queryClient 这三个核心,你就已经能应对 80% 的数据请求场景了。