目录
[与 React Query 的关系](#与 React Query 的关系)
[gcTime(原 cacheTime,v4.28.0+)](#gcTime(原 cacheTime,v4.28.0+))
[5、 实用配置建议](#5、 实用配置建议)
[1. 表单提交](#1. 表单提交)
[2. 带变量的更新操作](#2. 带变量的更新操作)
[3. 结合查询更新(自动重新获取)](#3. 结合查询更新(自动重新获取))
[查询 Hook](#查询 Hook)
[修改 Hook](#修改 Hook)
一、概述
官方文档:Installation | TanStack Query React Docs
主要功能
React Query 是一个专门用于数据获取、缓存、同步和更新的 React 库。
简单来说,这个库是专用用来维护接口数据的,类比可以参考Redux是专门做状态管理的库。
Redux 使用 Thunk 管理异步接口时,虽然能够实现功能,但相较于 React Query,往往需要编写更多代码,并且可能需要借助复杂的 Reselect 来实现缓存机制。而 React Query 在设计上更为简洁易维护,它提供了自动化的异步状态管理与缓存功能,从而显著提升了开发效率与应用的可维护性。
让Redux专注于管理状态,让React Query专注于管理接口。
核心价值
1、自动化加载和异常处理,减少样板代码;
2、智能缓存和后台更新,提升性能和用户体验;
3、将复杂的异步逻辑简化为声明式的Hooks;
4、提供强大的开发者工具便于调试;
5、是专用的服务器状态管理库。
与 React Query 的关系
React Query 是 v4 以前的叫法,从 v4 起就叫 TanStack Query。之所以改名字,是因为这个团队这套方案推广到除 React 之外的其他框架中去。到目前(2025年5月)最新的 v5 版本已经支持 React、Vue、Angular、Solid、Svelte 5 大框架。

使用步骤
1、创建一个ReactQuery实例;
2、导入QueryClientProvider组件;
3、用Provider包裹应用;
4、将client实例传递给Provider。
这里的Provider就是使用了React的Context来进行全局状态共享,让每个内部组件都可以通过useQuery获取到唯一的数据源。
javascript
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
// 1、在组件外实例化全局实例,用于数据共享
const queryClient = new QueryClient()
// 2、使用Provider包裹全局组件
<QueryClientProvider client={queryClient}>
<App/>
</QueryClientProvider>
// 3、调useQuery调用网络请求
const { data, isLoading, isError, isFetching } = useQuery({
queryKey: ["products"],
queryFn: () => reqQueryProducts()
})
// 4、组件上渲染
<Text>{data.data.username}</Text>
二、核心概念
1、useQuery
官方文档:useQuery | TanStack Query React Docs
queryKey
查询的唯一标识符,通常是一个数组。它被用于缓存、重新获取和在整个应用中共享数据。
queryFn
一个返回Promise的异步函数。你的实际数据获取逻辑(如:fetch、axios等)就放在这里。
enabled
当启用时,useQuery会进行查询,设置为false时,useQuery不会发其行动。如:在参数违规时可使用设置此属性为false来拒绝发送请求。
retry
错误重试的次数。
staleTime
数据的保鲜时间(有效时间),单位毫秒,当有效期过了后,再调用useQuery会重新请求数据并刷新缓存数据。在有效期内,再次获取数据会走缓存,不会重复请求数据。
gcTime
垃圾回收时间,单位毫秒,当用户未使用该数据且数据存在时间超过gcTime时,缓存数据会被直接清理掉,清理后下次访问需要重新获取数据。
返回结果
当我们调用了useQuery后,它会返回一个结果对象,对象结构如下:
参考文档:Queries | TanStack Query React Docs
|--------------|-----------------------------------------------------|
| 键 | 描述 |
| data | 成功解析出的数据。 |
| isLoading | 仅在首次获取时为true(无缓存)。缓存里已有数据就不会loading了。 |
| isFetching | 在任何请求进行中(包括后台刷新)时为true。每次获取数据都会生效。 |
| isError | 如果查询遇到错误,则为true。 |
| error | 如果查询处于 isError 状态,则通过 error 属性可以获得该错误。 |
| isPending | 查询尚无数据 |
| status | 查询的状态的字符串,与其他几个判断状态的属性有类似效果 |
| refetch | 刷新useQuery查询结果的函数,可调用此函数来清理userQuery的查询缓存结果并重新获取数据。 |
| isRefetching | 第一次获取数据不会生效,后面重复发数据就会生效。第一次获取数据时无效。 |
注:isLoading只在状态中完全没有请求数据时才为true;isFetching在第一次加载和后续的静默刷新时都为true。
2、useMutation
官方文档:useMutation | TanStack Query React Docs
是 React Query 中用于处理 创建、更新、删除 等操作(非 GET 请求)的 Hook。
关键记住:定义函数 → 创建 mutation → 调用 mutate。
目的:处理服务器端的副作用,如:POST、PUT、PATCH、DELETE。
mutationFn:执行数据变更的异步函数。
mutate:你调用来触发变更的函数。
onSuccess:变更成功后用于处理副作用的回调函数。
3、失效策略
失效并重新获取方式
官方文档:QueryClient | TanStack Query Docs
最常用和最安全的策略:失效并重新获取。
原理:使用queryClient.invalidateQueries将数据标记为"陈旧"。
优点:简单、安全,保持数据与服务器一致。
缺点:UI更新可能存在轻微的网络延迟。(用户可能看到短暂的loading页面)
乐观更新方式
为了极致 的用户体验,我们可以在服务器确认前立即更新 UI:乐观更新
原理:变更前手动更新缓存,若出错则回滚。
优点:最佳用户体验,感觉像是即时完成。
缺点:实现更复杂,需要小心处理错误和回滚逻辑。
场景:如点赞和收藏操作,先更新数据,若出现网络错误再进行自定义回滚操作,每次用户操作都会立即改变状态,以提高用户体验。
缓存
1、缓存查询
官方文档:useQuery | TanStack Query React Docs
javascript
const [id, setId] = useState<string | undefined>()
const { data, isLoading } = useQuery({
queryKey: ["products", id],
queryFn: () => reqQueryProducts(id)
})
在此场景中,通过 useQuery 查询商品列表中的单条数据时:
-
如果该
id之前已被查询过,useQuery将直接从缓存中获取已保存的数据,不会针对相同id发起重复请求; -
如果
id发生变化,useQuery会自动识别到变化,并获取新id对应的数据,保持数据与当前查询条件一致。
全局配置缓存
官方文档:QueryClient | TanStack Query Docs
javascript
import { QueryClient } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
gcTime: 1000 * 60 * 5, // 缓存保留时间(原 cacheTime)
staleTime: 1000 * 60, // 数据新鲜时间
},
},
});
各项配置详解
gcTime(原 cacheTime,v4.28.0+)
javascript
gcTime: 1000 * 60 * 5 // 默认 5 分钟
-
查询缓存过期后仍保留的时间(垃圾回收时间)
-
在此期间内再次访问相同查询会从缓存快速返回,然后后台重新获取
-
设为
0表示查询结束后立即移除缓存
-
v4.28.0+ :
cacheTime改为gcTime -
v5+ :继续使用
gcTime
staleTime
javascript
staleTime: 1000 * 60 * 10 // 默认 0 秒
-
数据保持新鲜的时间
-
在此时间内,相同查询不会重新获取
-
设为
Infinity表示数据永不过期
针对单个查询配置
javascript
useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
gcTime: 1000 * 60 * 10, // 10分钟
staleTime: 1000 * 60 * 5, // 5分钟内数据是新鲜的
});
更精细的缓存控制
基于查询键的缓存时间
javascript
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: (query) => {
// 根据 queryKey 返回不同的 staleTime
if (query.queryKey[0] === 'todos') {
return 1000 * 60 * 5;
}
return 0;
},
},
},
});
组合配置示例
javascript
useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
// 10分钟内数据新鲜
staleTime: 1000 * 60 * 10,
// 缓存保留1小时(即使组件卸载)
gcTime: 1000 * 60 * 60,
// 自动刷新选项
refetchOnWindowFocus: true,
refetchOnReconnect: true,
refetchOnMount: true,
});
2、缓存失效策略
主动失效缓存
javascript
// 使特定查询失效
queryClient.invalidateQueries({
queryKey: ['todos']
});
// 精确匹配
queryClient.invalidateQueries({
queryKey: ['todo', 1],
exact: true
});
3、设置缓存数据
官方文档:QueryClient | TanStack Query Docs
javascript
// 更新缓存数据
queryClient.setQueryData(['todo', id], newTodo);
4、移除缓存数据
官方文档:QueryClient | TanStack Query Docs
javascript
// 移除特定缓存
queryClient.removeQueries({ queryKey: ['todos'] });
// 垃圾回收(清除过期缓存)
queryClient.clear();
5、 实用配置建议
javascript
const queryClient = new QueryClient({
defaultOptions: {
queries: {
// 实时数据(聊天、通知)
gcTime: 1000 * 60 * 2, // 2分钟
staleTime: 0, // 总是重新获取
// 或静态数据(配置、字典)
// gcTime: 1000 * 60 * 60, // 1小时
// staleTime: Infinity, // 永不过期
},
},
});
// 不同场景的最佳实践
const strategies = {
realTime: {
staleTime: 0,
gcTime: 1000 * 60 * 2,
refetchInterval: 1000 * 30, // 30秒轮询
},
sessionData: {
staleTime: 1000 * 60 * 5,
gcTime: 1000 * 60 * 30,
},
staticData: {
staleTime: Infinity,
gcTime: 1000 * 60 * 60 * 24, // 24小时
},
};
根据你的应用场景选择合适的时间配置,实时数据建议较短的 staleTime,静态数据可以设置较长的缓存时间。
异常处理
核心要点总结:
-
必用属性 :
error,isError,refetch -
常用配置 :
retry,retryDelay,onError -
推荐实践:全局配置 + 具体查询覆盖
-
错误分类:网络错误、服务器错误、客户端错误
-
用户友好:显示清晰错误信息,提供重试按钮
错误重试案例


在模拟请求异常过程中,我们可以观察到,请求出错后 useQuery 自动进行了 3 次错误重试,并最终输出以下信息。这体现了 useQuery 对请求失败的有效异常处理机制。
基础用法
javascript
import { useQuery } from '@tanstack/react-query';
const {
data,
error, // 错误对象
isError, // 是否有错误(布尔值)
isPending, // 加载中(新版替代isLoading)
refetch // 重新获取函数
} = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
// 核心配置
retry: 1, // 失败时重试1次
retryDelay: 1000, // 重试延迟1秒
// 错误回调
onError: (error) => {
console.error('查询失败:', error);
}
});
简单错误显示
javascript
function Todos() {
const { data, error, isError, isPending } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
});
if (isPending) return <div>加载中...</div>;
if (isError) {
return (
<div>
<p>错误: {error.message}</p>
<button onClick={() => refetch()}>重试</button>
</div>
);
}
return <div>{/* 显示数据 */}</div>;
}
重试策略
javascript
useQuery({
queryKey: ['user'],
queryFn: fetchUser,
// 条件重试
retry: (failureCount, error) => {
// 只在网络错误或服务器错误时重试
if (error.status >= 500) return failureCount < 3; // 服务器错误重试3次
if (error.message.includes('Network')) return failureCount < 2; // 网络错误重试2次
return false; // 其他错误不重试
},
// 指数退避延迟
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
});
全局配置(推荐)
javascript
// src/app.js
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
// 创建时配置全局错误处理
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 2, // 所有查询默认重试2次
onError: (error) => {
// 全局错误处理(如发送到监控服务)
console.error('全局查询错误:', error);
},
},
mutations: {
onError: (error) => {
console.error('全局变更错误:', error);
},
},
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* 你的应用 */}
</QueryClientProvider>
);
}
实用错误处理函数
javascript
// 统一错误处理函数
function handleQueryError(error) {
if (error.response?.status === 401) {
// 未授权,跳转登录
window.location.href = '/login';
} else if (error.response?.status === 404) {
// 资源不存在
return { type: 'not-found', message: '请求的内容不存在' };
} else if (error.message.includes('Network')) {
// 网络错误
return { type: 'network', message: '网络连接失败,请检查网络' };
} else {
// 其他错误
return { type: 'error', message: error.message || '请求失败' };
}
}
// 使用示例
const { data, error, isError } = useQuery({
queryKey: ['data'],
queryFn: fetchData,
onError: handleQueryError,
});
错误抛出案例
javascript
function DataComponent() {
const { data, error, isError, isPending, refetch } = useQuery({
queryKey: ['data'],
queryFn: () => fetch('/api/data').then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}),
retry: 1,
});
if (isPending) return <div>加载中...</div>;
if (isError) return (
<div>
<p>错误: {error.message}</p>
<button onClick={() => refetch()}>重试</button>
</div>
);
return <div>{JSON.stringify(data)}</div>;
}
增删改副作用
1、基础使用
javascript
import { useMutation } from '@tanstack/react-query';
// 1. 定义 API 函数
const addUser = async (newUser) => {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newUser),
});
return response.json();
};
// 2. 在组件中使用
function AddUserForm() {
const mutation = useMutation({
mutationFn: addUser,
// 可选配置
onSuccess: (data) => {
console.log('添加成功:', data);
},
onError: (error) => {
console.error('添加失败:', error);
},
onSettled: () => {
console.log('请求完成(无论成功或失败)');
},
});
const handleSubmit = (formData) => {
mutation.mutate(formData);
};
return (
<div>
<button
onClick={() => handleSubmit({ name: 'John' })}
disabled={mutation.isPending}
>
{mutation.isPending ? '添加中...' : '添加用户'}
</button>
{mutation.isError && (
<div>错误: {mutation.error.message}</div>
)}
{mutation.isSuccess && (
<div>用户添加成功!</div>
)}
</div>
);
}
2、核心状态和函数
javascript
const mutation = useMutation({
mutationFn: yourApiFunction,
});
// 主要状态
mutation.status // 'idle' | 'pending' | 'error' | 'success'
mutation.isIdle // true | false
mutation.isPending // true | false
mutation.isError // true | false
mutation.isSuccess // true | false
// 数据和错误
mutation.data // 成功返回的数据
mutation.error // 错误对象
// 主要方法
mutation.mutate(data) // 触发 mutation
mutation.reset() // 重置状态
// 异步版本
mutation.mutateAsync(data) // 返回 Promise
3、实用示例
1. 表单提交
javascript
function RegisterForm() {
const [formData, setFormData] = useState({ email: '', password: '' });
const registerMutation = useMutation({
mutationFn: registerUser,
onSuccess: () => {
// 清空表单
setFormData({ email: '', password: '' });
alert('注册成功!');
},
});
const handleSubmit = (e) => {
e.preventDefault();
registerMutation.mutate(formData);
};
return (
<form onSubmit={handleSubmit}>
<input
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
/>
<button type="submit" disabled={registerMutation.isPending}>
注册
</button>
</form>
);
}
2. 带变量的更新操作
javascript
const updateUser = useMutation({
mutationFn: ({ userId, userData }) =>
fetch(`/api/users/${userId}`, {
method: 'PUT',
body: JSON.stringify(userData),
}).then(res => res.json()),
});
// 使用时传递对象
updateUser.mutate({
userId: '123',
userData: { name: '新的名字' }
});
3. 结合查询更新(自动重新获取)
javascript
const queryClient = useQueryClient();
const addTodo = useMutation({
mutationFn: postNewTodo,
onSuccess: () => {
// 使旧的缓存失效,触发重新获取
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
// 或者乐观更新
const addTodoOptimistic = useMutation({
mutationFn: postNewTodo,
onMutate: async (newTodo) => {
// 取消正在进行的 todos 查询
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'] });
},
});
4、常用配置选项
javascript
const mutation = useMutation({
mutationFn: apiFunction,
// 生命周期钩子
onMutate: (variables) => {
// 在 mutation 执行前调用,常用于乐观更新
},
onSuccess: (data, variables, context) => {
// 成功时调用
},
onError: (error, variables, context) => {
// 失败时调用
},
onSettled: (data, error, variables, context) => {
// 完成时调用(无论成功失败)
},
// 重试配置
retry: 3, // 失败时重试次数
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
});
结合Typescript语法
查询 Hook
javascript
// hooks/useUsers.ts
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
import { User } from '@/types/user';
// API 函数
const fetchUsers = async (): Promise<User[]> => {
const response = await fetch('/api/users');
if (!response.ok) throw new Error('Failed to fetch users');
return response.json();
};
export const useUsers = (options?: UseQueryOptions<User[], Error>) => {
return useQuery<User[], Error>({
queryKey: ['users'], // 查询键
queryFn: fetchUsers, // 查询函数
...options,
});
};
// 带参数的查询
export const useUser = (id: number) => {
return useQuery<User, Error>({
queryKey: ['user', id], // 依赖 id 的查询键
queryFn: async () => {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error('Failed to fetch user');
return response.json();
},
enabled: !!id, // 只有 id 存在时才启用查询
});
};
修改 Hook
javascript
// hooks/useUserMutations.ts
import {
useMutation,
useQueryClient,
UseMutationOptions,
} from '@tanstack/react-query';
import { User, CreateUserDto, UpdateUserDto } from '@/types/user';
export const useCreateUser = () => {
const queryClient = useQueryClient();
return useMutation<User, Error, CreateUserDto>({
mutationFn: async (newUser) => {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newUser),
});
if (!response.ok) throw new Error('Failed to create user');
return response.json();
},
onSuccess: () => {
// 使 users 查询失效,触发重新获取
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
};
export const useUpdateUser = () => {
const queryClient = useQueryClient();
return useMutation<User, Error, { id: number; data: UpdateUserDto }>({
mutationFn: async ({ id, data }) => {
const response = await fetch(`/api/users/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) throw new Error('Failed to update user');
return response.json();
},
onSuccess: (data, variables) => {
// 更新缓存
queryClient.setQueryData(['user', variables.id], data);
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
};
export const useDeleteUser = () => {
const queryClient = useQueryClient();
return useMutation<void, Error, number>({
mutationFn: async (id) => {
const response = await fetch(`/api/users/${id}`, {
method: 'DELETE',
});
if (!response.ok) throw new Error('Failed to delete user');
},
onSuccess: (_, id) => {
// 移除缓存
queryClient.removeQueries({ queryKey: ['user', id] });
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
};
总结到此!