TanStack Query(前身 React Query)是目前 React 生态中最主流的服务端状态管理库。它把“服务端数据”视作一种特殊的状态,提供了一套完整的获取、缓存、同步和更新机制。以下四个核心能力解决了前端数据请求中最常见的痛点。
1. 数据缓存
痛点: 传统方式下,每次组件挂载或页面切换都会重新请求数据,导致不必要的网络开销和加载闪烁。
TanStack Query 如何解决:
每个接口请求都由唯一的 queryKey 标识,返回的数据会被存入内存缓存。当同一个 queryKey 再次被使用时,直接从缓存返回数据,同时可以选择在后台重新发起请求以保持数据新鲜。这避免了重复请求,也让页面切换瞬间呈现已有数据,体验如同本地数据。
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) {
const { data } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
staleTime: 5 * 60 * 1000, // 5分钟内缓存视为新鲜
});
return <div>{data?.name}</div>;
}
实用要点:
staleTime控制数据“新鲜度”,在此时间内不会触发后台重取。cacheTime(v5 中改为gcTime)控制缓存垃圾回收时间,组件卸载后数据仍保留一段时间,再次挂载可立即使用。
2. 自动重取
痛点: 数据过期、窗口重新聚焦、网络恢复等场景下,需要手动刷新数据。
TanStack Query 如何解决:
提供多种自动重新获取策略,保证用户看到的始终是“尽力最新”的数据。默认情况下:
- 数据变为“stale”时,下一次查询会自动后台重取。
- 浏览器窗口重新获得焦点(
refetchOnWindowFocus)时重取。 - 网络断开后重新连接时重取。
const { data } = useQuery({
queryKey: ['notifications'],
queryFn: fetchNotifications,
refetchInterval: 10000, // 每10秒自动轮询
});
实用要点:
- 可按需关闭自动重取(如对实时性要求不高的数据),通过配置
refetchOnWindowFocus: false等。 - 提供
refetchInterval用于轮询场景,代码简洁,无需自己管理定时器。
3. 乐观更新
痛点: 用户提交操作(如添加、删除、修改)后,需要等待服务端响应才更新 UI,这会产生明显的操作延迟。
TanStack Query 如何解决:
通过 useMutation,你可以在请求发起前 预先修改缓存数据,从而立即更新 UI。如果服务端请求失败,自动回滚到修改前的状态。
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (newTodo) => fetch('/api/todos', { method: 'POST', body: newTodo }),
onMutate: async (newTodo) => {
await queryClient.cancelQueries({ queryKey: ['todos'] });
const previousTodos = queryClient.getQueryData(['todos']);
// 乐观地将新项插入缓存
queryClient.setQueryData(['todos'], old => [...old, newTodo]);
return { previousTodos }; // 用于回滚
},
onError: (err, newTodo, context) => {
// 回滚到乐观更新前的状态
queryClient.setQueryData(['todos'], context.previousTodos);
},
onSettled: () => {
// 无论成功或失败,最终从服务端重新获取以保证数据一致
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
实用要点:
- 乐观更新极大提升交互流畅度,尤其适用于即时反馈场景(如点赞、删除评论)。
- 必须实现回滚机制,否则网络错误会导致 UI 与服务端状态不一致。
- 简单场景也可直接使用
mutation.onSuccess手动更新缓存,避免复杂控制。
4. 分页 / 无限滚动
痛点: 传统分页需要手动管理当前页码、总页数、翻页逻辑;无限滚动还需要处理加载更多、判断是否到底等复杂边界。
TanStack Query 如何解决:
- 分页:
useQuery将页码放入queryKey,切换页码时自动请求新数据,缓存独立,前进后退体验极佳。keepPreviousData(v5 中为placeholderData)让翻页时显示旧数据,避免页面抖动。 - 无限滚动:
useInfiniteQuery专门处理“加载更多”场景,提供fetchNextPage、hasNextPage、isFetchingNextPage等属性,配合滚动事件即可实现。
分页示例:
const [page, setPage] = useState(1);
const { data, isLoading } = useQuery({
queryKey: ['projects', page],
queryFn: () => fetch(`/api/projects?page=${page}`).then(res => res.json()),
placeholderData: keepPreviousData, // 翻页时保持旧数据
});
无限滚动示例:
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
queryKey: ['posts'],
queryFn: ({ pageParam = 1 }) => fetch(`/api/posts?cursor=${pageParam}`),
getNextPageParam: (lastPage) => lastPage.nextCursor || undefined,
});
// 配合 Intersection Observer,当底部哨兵出现时加载下一页
实用要点:
- 列表数据建议使用
useInfiniteQuery替代传统“加载更多”按钮,尤其是移动端。 - 分页场景使用
placeholderData+isPlaceholderData标识,可在数据未返回时显示上一个页面的数据或骨架屏。 - 两种模式都内置了请求去重、缓存和自动管理,开发者只需关注数据获取逻辑。
TanStack Query 通过上述能力,将服务端状态管理从“命令式请求处理”提升为“声明式数据同步”,显著简化了前端数据交互逻辑,同时带来更流畅的用户体验。