TanStack(以前叫 React Query 的作者团队)是一个专注于构建数据获取和状态管理工具 的开源组织,其最著名的项目是 TanStack Query (以前叫 React Query)。虽然名字里有 "React",但 TanStack 现在已发展为一个框架无关的工具集,支持 React、Vue、Svelte、Solid 等多种前端框架。
核心作用:解决数据获取(Data Fetching)问题
在传统 React 应用中,开发者常常需要手动处理:
- 发起 API 请求
- 缓存数据
- 处理加载/错误状态
- 自动重新获取(如窗口聚焦时)
- 分页、无限滚动、预取等高级功能
而 TanStack Query(React Query) 通过声明式的方式自动处理这些复杂逻辑,让你更专注于 UI 和业务逻辑。
TanStack 主要项目包括:
| 项目 | 用途 |
|---|---|
| TanStack Query | 数据获取、缓存、同步、后台更新等(核心产品) |
| TanStack Router | 类型安全、基于文件系统的路由(适用于 React) |
| TanStack Table | 高性能、可定制的表格组件(支持虚拟滚动、排序、过滤等) |
| TanStack Form | 表单状态管理(仍在开发中,目标是类型安全、高性能) |
| TanStack Virtual | 虚拟滚动(用于长列表优化性能) |
所有这些库都强调:类型安全(TypeScript 优先)、高性能、零依赖、框架无关(或适配多框架)。
TanStack Query: keepPreviousData
keepPreviousData 是 @tanstack/react-query(旧称 React Query)中 useQuery 的一个可选配置选项,它的作用是:
当查询键(
queryKey)发生变化、触发新请求时,在新数据加载完成前,继续保留并显示上一次的数据(而不是立即变成undefined或重置为初始状态)。
🎯 解决什么问题?
在分页、筛选、搜索等场景中,用户切换参数(比如从第 1 页切到第 2 页),queryKey 会变化,从而触发新的 useQuery 请求。
默认行为:
- 一旦
queryKey改变,当前data会被立即清除(变为undefined) - UI 会短暂显示"加载中"或空白,即使上一页的数据仍然有效
开启 keepPreviousData: true 后:
- 即使
queryKey变了,上一次的data仍会保留 - 直到新数据加载完成,才替换为新数据
- 用户体验更流畅,避免闪烁或空白
✅ 使用示例:分页场景
javascript
import { useQuery } from '@tanstack/react-query';
function UserList({ page }) {
const { data, isLoading, isFetching } = useQuery({
queryKey: ['users', page],
queryFn: () => fetch(`/api/users?page=${page}`).then(res => res.json()),
keepPreviousData: true, // 👈 关键配置
});
if (isLoading) return <div>Loading...</div>;
return (
<div>
{/* 即使 page 变化,data 也不会立即消失 */}
{data?.users.map(user => <div key={user.id}>{user.name}</div>)}
{isFetching && <div>加载下一页...</div>} {/* 注意:用 isFetching 而不是 isLoading */}
</div>
);
}
💡 注意:在
keepPreviousData: true时,应使用isFetching来判断是否正在加载新数据,因为isLoading在有缓存数据时可能为false。
⚠️ 注意事项
-
不会阻止新请求:它只是保留旧数据,新请求照常发起。
-
不影响缓存:TanStack Query 依然会按正常逻辑缓存新旧数据。
-
适用于"相似数据结构"的场景:比如分页、搜索、标签切换等。如果新旧数据结构完全不同,保留旧数据可能导致 UI 错误。
-
v4+ 中已弃用?
实际上,在 TanStack Query v4 及以上版本中,
keepPreviousData仍然是有效选项 ,但官方推荐优先使用placeholderData或initialData来实现类似效果(更灵活)。不过keepPreviousData依然广泛使用且支持。补充:从 v5 开始,
keepPreviousData已被移除 !如果你使用的是 v5+,应该改用:
placeholderData: keepPreviousData()其中
keepPreviousData是一个 helper 函数(需从@tanstack/react-query导入)。✅ v5+ 正确写法
javascriptimport { keepPreviousData, useQuery } from '@tanstack/react-query'; useQuery({ queryKey: ['users', page], queryFn: fetchUsers, placeholderData: keepPreviousData, // 👈 v5+ 方式 });
总结
| 版本 | 用法 |
|---|---|
| v3 / v4 | keepPreviousData: true |
| v5+ | placeholderData: keepPreviousData(作为函数传入) |
核心目的 :在参数变化导致查询刷新时,保持 UI 稳定,避免数据闪空,提升用户体验。
TanStack Query: enabled
在 @tanstack/react-query 的 useQuery 中,enabled: Boolean 是一个条件控制选项 ,用于决定该查询 是否自动执行。
✅ 核心作用
- 当
enabled为true(或真值)时:自动发起请求(即"挂载时自动 fetch")。 - 当
enabled为false(或假值)时:不自动发起请求,即使组件已挂载。
这常用于 按条件启用/禁用查询 的场景。
📌 例子解释
javascript
useQuery({
queryKey: ['enterpriseData'],
queryFn: fetchEnterpriseData,
enabled: IS_ENTERPRISE, // ← 假设 IS_ENTERPRISE 是一个布尔值
});
-
如果
IS_ENTERPRISE === true:→ 组件一渲染,就自动调用
fetchEnterpriseData()获取企业数据。 -
如果
IS_ENTERPRISE === false:→ 完全不会发起网络请求 ,
data保持为undefined,isLoading为false。→ 此时你可以安全地忽略这个查询,比如普通用户不需要加载企业专属数据。
🔧 典型使用场景
-
权限控制
只有企业用户才加载某些敏感或高级数据:
enabled: user?.role === 'enterprise' -
依赖前置数据
必须等某个 ID 有值才发起请求:
enabled: !!userId -
懒加载 / 手动触发
结合
refetch()实现点击按钮再加载const { refetch } = useQuery({ ..., enabled: false }); const handleClick = () => refetch(); -
环境判断
仅在生产环境或特定环境下启用
enabled: process.env.NODE_ENV === 'production'
⚠️ 注意事项
- 即使
enabled: false,你仍然可以通过refetch()手动触发请求。 enabled变为true后,会立即自动执行查询(如果数据不在缓存中或已过期)。- 不要和
staleTime、cacheTime混淆 ------enabled控制的是 是否发起请求,而不是缓存行为。
💡 小技巧:组合使用
const { data, isLoading, refetch } = useQuery({
queryKey: ['report', reportId],
queryFn: () => api.getReport(reportId),
enabled: !!reportId && IS_ENTERPRISE, // 两个条件都满足才加载
});
这样可以避免因 reportId 为空或用户无权限而发出无效请求。
总结
enabled: IS_ENTERPRISE的意思是:只有当IS_ENTERPRISE为真时,这个查询才会自动运行;否则,它会被"禁用",不发请求、不报错、安静等待。
这是 TanStack Query 中实现条件化数据获取的关键机制,能有效减少不必要的网络请求,提升性能和安全性。
TanStack Query: retry
在 @tanstack/react-query 的 useQuery 中,retry: false 表示:
当查询函数(
queryFn)抛出错误时,不进行任何重试,立即标记该查询为"失败"状态。
📌 默认行为 vs 设置 retry: false
-
默认情况下 (未配置
retry):- TanStack Query 会在查询失败后 自动重试 3 次(指数退避延迟:~1s、2s、4s...)。
- 这对临时性网络问题或服务抖动很有用。
-
设置
retry: false后:- 一旦请求失败(比如 API 返回 500 或网络断开),立刻停止,不再重试。
- 错误会立即暴露给 UI(通过
error和isError)。
✅ 使用示例
javascript
const { data, error, isError, isLoading } = useQuery({
queryKey: ['userData'],
queryFn: fetchUserData,
retry: false, // ← 失败就失败,别重试!
});
如果 fetchUserData() 抛出异常:
- 不会再尝试第 2、3、4 次
isError立即变为trueerror包含第一次失败的错误对象
🔧 retry 的其他取值
| 值 | 行为 |
|---|---|
false |
完全不重试(推荐用于用户操作类请求,如提交表单) |
true 或 3(数字) |
最多重试 3 次(默认是 3) |
(failureCount, error) => boolean |
自定义重试逻辑(例如只对 5xx 重试) |
自定义重试示例(仅对服务器错误重试):
javascript
retry: (failureCount, error) => {
// 只有 HTTP 5xx 错误才重试,最多 2 次
return error.statusCode >= 500 && failureCount < 2;
}
🎯 什么时候用 retry: false?
-
用户主动触发的操作
比如点击"获取验证码",失败了应该立刻提示用户,而不是默默重试。
-
已知不可恢复的错误
如 401(未授权)、403(禁止访问)、404(资源不存在)------这些错误重试也没用。
-
避免干扰用户体验
如果错误需要用户干预(如输入正确信息),自动重试反而会造成混乱。
-
调试阶段
快速看到首次错误,便于排查问题。
⚠️ 注意
retry只对抛出异常(reject)的queryFn生效 。如果你在queryFn里catch了错误并返回了正常值,Query 会认为"成功",不会触发重试逻辑。- 即使
retry: false,你仍然可以通过 UI 按钮调用refetch()手动重试。
TanStack Query: useQueryClient
useQueryClient 是 @tanstack/react-query 提供的一个 React Hook,它的作用是:
让你在组件中获取当前应用的
QueryClient实例,从而可以手动操作 React Query 的缓存、触发 refetch、预取数据、清除缓存等高级操作。
🧠 为什么需要它?
React Query 默认自动管理数据获取和缓存,但在很多实际场景中,你可能需要主动干预缓存或查询行为,比如:
- 用户提交表单后,手动刷新某个列表(如"新增用户后刷新用户列表")
- 乐观更新(Optimistic Update):先更新 UI,再同步到服务器,失败则回滚
- 预取数据(Prefetching):在用户可能访问某页前预先加载数据
- 使某些查询失效(invalidateQueries):让缓存过期,下次自动重新请求
- 直接设置/修改缓存数据(setQueryData)
这些操作都需要通过 QueryClient 实例完成,而 useQueryClient() 就是在函数组件中获取这个实例的标准方式。
✅ 基本用法
javascript
import { useQueryClient } from '@tanstack/react-query';
function UserProfile({ userId }) {
const queryClient = useQueryClient();
const handleUpdateUser = async (newData) => {
// 1. 乐观更新:先更新缓存中的数据
queryClient.setQueryData(['user', userId], (oldData) => ({
...oldData,
...newData,
}));
try {
// 2. 发送请求到服务器
await updateUser(userId, newData);
} catch (error) {
// 3. 失败时回滚缓存
queryClient.invalidateQueries({ queryKey: ['user', userId] });
}
};
return <button onClick={() => handleUpdateUser({ name: 'New Name' })}>更新</button>;
}
🔧 常用方法(通过 queryClient 调用)
| 方法 | 用途 |
|---|---|
setQueryData(queryKey, updater) |
直接写入缓存(用于乐观更新、本地修改) |
getQueryData(queryKey) |
读取当前缓存中的数据(不触发请求) |
invalidateQueries({ queryKey: [...] }) |
标记查询为无效,下次使用时自动重新 fetch |
refetchQueries({ queryKey: [...] }) |
立即重新请求匹配的查询 |
prefetchQuery({ queryKey, queryFn }) |
提前加载数据到缓存(常用于 hover 或滚动预加载) |
removeQueries({ queryKey: [...] }) |
从缓存中移除特定查询 |
cancelQueries(...) |
取消正在进行的查询 |
🌰 实际场景示例:提交后刷新列表
javascript
function CreateUserForm() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (userData) => createUser(userData),
onSuccess: () => {
// 成功后让 users 列表缓存失效,自动重新加载
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
return (
<form onSubmit={(e) => {
e.preventDefault();
mutation.mutate({ name: 'Alice' });
}}>
<button type="submit">创建用户</button>
</form>
);
}
⚠️ 注意事项
useQueryClient()必须在<QueryClientProvider>的子组件中使用。- 不要滥用
setQueryData,确保数据结构与原始查询一致。 - 在 v4/v5 中 API 略有变化(如
invalidateQueries的参数格式),建议查阅对应版本文档。
总结
useQueryClient() 是你与 React Query 缓存系统交互的"控制台" 。
它赋予你在自动数据管理之外的手动控制能力,是实现高级交互(如乐观更新、缓存同步、预加载)的关键工具。
官方文档参考:https://tanstack.com/query/latest/docs/react/reference/useQueryClient
TanStack Query: useMutation
useMutation 是 @tanstack/react-query(TanStack Query)提供的一个 React Hook ,专门用于处理需要改变服务器状态的操作,比如:
- 创建数据(POST)
- 更新数据(PUT / PATCH)
- 删除数据(DELETE)
- 登录、上传文件、发送消息等具有副作用的异步操作
🎯 核心作用
封装并简化"写操作"(mutations)的流程,自动处理加载状态、错误、成功回调、缓存更新等常见逻辑。
与 useQuery(用于"读")不同,useMutation 用于"写",它不会自动执行 ,而是返回一个函数(通常叫 mutate 或 mutateAsync),由你手动触发。
✅ 基本用法
javascript
import { useMutation } from '@tanstack/react-query';
function CreateUserForm() {
const { mutate, isLoading, isError, error, isSuccess } = useMutation({
mutationFn: (newUser) =>
fetch('/api/users', {
method: 'POST',
body: JSON.stringify(newUser),
}).then(res => res.json()),
// 可选:成功后的回调
onSuccess: (data) => {
console.log('用户创建成功:', data);
// 通常在这里让相关查询失效或直接更新缓存
},
// 可选:失败后的回调
onError: (error) => {
console.error('创建失败:', error);
}
});
const handleSubmit = (e) => {
e.preventDefault();
const newUser = { name: 'Alice' };
mutate(newUser); // 👈 手动触发 mutation
};
if (isLoading) return <div>提交中...</div>;
if (isError) return <div>错误:{error.message}</div>;
if (isSuccess) return <div>✅ 创建成功!</div>;
return <form onSubmit={handleSubmit}>...</form>;
}
🔑 关键特性
| 特性 | 说明 |
|---|---|
| 手动触发 | 必须调用 mutate(variables) 才会执行 |
| 状态管理 | 自动提供 isLoading、isError、isSuccess、data、error 等状态 |
| 重试机制 | 支持 retry 配置(默认为 0,即不重试,因为写操作通常不可重试) |
| 缓存联动 | 成功后可通过 onSuccess 调用 queryClient.invalidateQueries() 刷新相关查询,或使用 setQueryData/setQueriesData 直接更新缓存(乐观更新) |
| 取消/清理 | 支持在 mutation 函数中处理取消信号(如 AbortController) |
🔄 与缓存交互(最佳实践)
Mutation 通常会影响已有的查询数据,推荐在 onSuccess 中使相关查询失效,让 TanStack Query 自动重新拉取最新数据:
javascript
import { useMutation, useQueryClient } from '@tanstack/react-query';
const queryClient = useQueryClient();
useMutation({
mutationFn: updateUser,
onSuccess: () => {
// 使所有以 ['users'] 开头的查询失效
queryClient.invalidateQueries({ queryKey: ['users'] });
// 或精确失效某个用户
// queryClient.invalidateQueries({ queryKey: ['user', userId] });
}
});
💡 更高级的做法:乐观更新(Optimistic Update)
在请求发出前就更新 UI,失败再回滚,体验更流畅。
✅ 目标 :用户点击"添加待办事项"后,立刻在 UI 上显示新任务 (即使网络请求还没完成),如果请求失败,自动回滚,删除刚才添加的假数据。
⚠️ 注意事项
-
不要用于"读"操作 :
useMutation是为"写"设计的,不要用它来获取数据。 -
默认不重试:因为写操作(如创建订单)重复执行可能造成副作用。
-
变量传递 :
mutate(variables)中的variables会作为唯一参数传给mutationFn。 -
异步版本 :
mutateAsync返回 Promise,适合需要await的场景:javascripttry { const data = await mutateAsync(newUser); } catch (error) { // 处理错误 }
总结
useMutation是 TanStack Query 中处理"写操作"的标准方式,它帮你管理异步状态、错误处理,并无缝集成缓存更新机制,让你专注业务逻辑而非样板代码。
适用于:
- 表单提交
- 删除按钮
- 点赞/收藏
- 文件上传
- 用户登录/注册等任何改变服务器状态的操作
官方文档:https://tanstack.com/query/latest/docs/react/guides/mutations
**TanStack Table:**useReactTable
useReactTable 是 @tanstack/react-table (TanStack Table v8+)中的核心 Hook,用于在 React 应用中创建和管理一个功能强大、高度可定制的表格实例。
🎯 核心作用
useReactTable 接收表格的配置(如数据、列定义、分页、排序、筛选等),返回一个表格实例对象,该对象包含:
- 表格的状态(当前页、排序字段、筛选值等)
- 表格的元数据(行、列、单元格结构)
- 用于渲染和交互的方法(如
getRowModel(),getHeaderGroups()等)
你用它来驱动 UI 渲染,而 TanStack Table 负责处理所有复杂的表格逻辑。
💡 它本身不渲染任何 DOM,只提供数据和逻辑 ------ 你需要自己用 JSX 渲染表格结构(完全控制样式和行为)。
✅ 基本使用示例
javascript
import { useReactTable, getCoreRowModel } from '@tanstack/react-table';
import { useMemo } from 'react';
function MyTable({ data, columns }) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(), // 必需:获取基础行模型
});
return (
<table>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th key={header.id}>
{header.isPlaceholder ? null : header.renderHeader()}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map(row => (
<tr key={row.id}>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>{cell.renderCell()}</td>
))}
</tr>
))}
</tbody>
</table>
);
}
🔧 支持的高级功能(通过插件/选项启用)
useReactTable 的强大之处在于它的插件化架构。你可以按需启用功能:
| 功能 | 配置方式 |
|---|---|
| 分页 | getPaginationRowModel: getPaginationRowModel() |
| 排序 | getSortedRowModel: getSortedRowModel() + 列定义 enableSorting: true |
| 筛选 | getFilteredRowModel: getFilteredRowModel() + 列定义 filterFn |
| 行选择 | enableRowSelection: true + getIsSelected() 等方法 |
| 列可见性 | enableHiding: true |
| 列尺寸调整 | 需配合 flexRender 和 CSS 或第三方库 |
| 虚拟滚动 | 需结合 @tanstack/virtual |
例如,启用分页和排序:
javascript
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});
然后你可以调用:
table.nextPage()table.previousPage()table.setSorting([{ id: 'name', desc: false }])
📦 关键特点
- 框架无关核心 :TanStack Table 的核心是框架无关的,
useReactTable是其 React 封装。 - 零样式:不带任何默认样式,完全由你控制外观(适合 Tailwind、CSS-in-JS、Ant Design 等)。
- 高性能:支持大数据集(配合虚拟滚动)、细粒度更新。
- 类型安全:对 TypeScript 支持极佳,列定义、数据类型自动推导。
- 组合式 API:按需引入功能,避免打包体积膨胀。
🆚 与旧版(React Table v7)的区别
- v7 使用
useTable()+ 大量 HOC 插件(如useSortBy,usePagination) - v8+(TanStack Table)改用
useReactTable()+ 函数式插件(如getSortedRowModel),更灵活、更函数式、更好 TypeScript 支持
官方推荐使用场景
- 数据仪表盘
- 后台管理系统
- 需要复杂交互的表格(多级表头、拖拽、编辑、展开行等)
- 对性能或定制性要求高的项目
总结
useReactTable是 TanStack Table 在 React 中的入口 Hook,它将你的数据和列配置转换为一个功能完整的表格逻辑引擎,让你自由地构建高性能、可交互的表格 UI。
官网文档:https://tanstack.com/table/latest/docs/guide/introduction
GitHub 示例:https://github.com/TanStack/table/tree/main/examples/react
如果你需要分页、排序、筛选、选择等企业级表格功能,useReactTable 是目前 React 生态中最强大且灵活的选择之一。
举个 React + TanStack Query 的简单例子:
javascript
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) {
const { data, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>Hello, {data.name}!</div>;
}
TanStack Query 会自动:
- 缓存
user数据(相同queryKey不会重复请求) - 在组件重新挂载时返回缓存数据(瞬时显示)
- 后台自动刷新(可配置)
- 处理竞态条件(race conditions)
总结
TanStack 是一套现代化前端工具库集合,核心目标是:
让数据获取、路由、表格、表单等常见任务变得更简单、更高效、更类型安全。
如果你在用 React 做数据密集型应用(如后台管理系统、仪表盘等),TanStack Query 几乎是行业标准,强烈推荐使用。