1 概述
@tanstack/react-query 是一个功能强大的异步状态管理库,专为简化 React 应用中的服务器状态管理而设计。它提供了一套全面的工具集,用于处理数据获取、缓存、同步和更新服务器状态,使开发者能够专注于业务逻辑而非数据获取细节。
作为现代前端开发的关键工具,@tanstack/react-query 解决了传统数据获取方案中的诸多痛点:
- 自动缓存与失效管理,减少不必要的网络请求
- 智能重试与背景刷新机制,提升用户体验
- 简洁的 API 设计,降低异步数据处理的复杂性
- 深度整合 TypeScript,提供完善的类型安全保障
- 与 React 生态无缝集成,支持 Suspense 等现代特性
最新版本 v5.84.2 带来了多项重要改进,包括统一的参数对象化 API、增强的类型推断能力、优化的缓存策略以及对 React 18 特性的更好支持。这些更新进一步提升了库的易用性和性能,使其成为构建高性能 React 应用的理想选择。
无论是小型项目还是大型企业级应用,@tanstack/react-query 都能显著简化数据管理流程,减少样板代码,并提供一致且可预测的状态管理体验。
2 安装与配置
2.1 环境要求
@tanstack/react-query v5 需要以下环境支持:
- React: 18.0.0 或更高版本
- TypeScript: 4.7 或更高版本(可选)
- Node.js: 16.0.0 或更高版本
2.2 安装命令
使用 npm
bash
# 安装核心库
npm install @tanstack/react-query
# 安装开发工具(可选,用于调试)
npm install --save-dev @tanstack/react-query-devtools
# 如果使用 TypeScript,确保安装类型定义
npm install --save-dev @types/react @types/react-dom
使用 yarn
bash
# 安装核心库
yarn add @tanstack/react-query
# 安装开发工具
yarn add -D @tanstack/react-query-devtools
使用 pnpm
bash
# 安装核心库
pnpm add @tanstack/react-query
# 安装开发工具
pnpm add -D @tanstack/react-query-devtools
安装指定版本
如需安装特定版本(如本文介绍的 v5.84.2):
bash
npm install @tanstack/react-query@5.84.2
npm install --save-dev @tanstack/react-query-devtools@5.84.2
2.3 项目初始化
创建新项目(可选)
如果您正在创建新的 React 项目,推荐使用以下方式:
bash
# 使用 Create React App
npx create-react-app my-app --template typescript
cd my-app
# 或使用 Vite(推荐,更快的开发体验)
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
# 安装 React Query
npm install @tanstack/react-query @tanstack/react-query-devtools
2.4 基本配置
步骤 1:创建 QueryClient
在 src/lib/queryClient.ts
文件中创建 QueryClient 实例:
tsx
// src/lib/queryClient.ts
import { QueryClient } from '@tanstack/react-query'
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 数据保鲜时间:1分钟
gcTime: 10 * 60 * 1000, // 垃圾回收时间:10分钟
retry: 1, // 失败重试次数
refetchOnWindowFocus: true, // 窗口聚焦时重新获取
refetchOnReconnect: true, // 网络重连时重新获取
},
mutations: {
retry: 1, // 变更操作重试次数
},
},
})
步骤 2:配置应用入口
在 src/main.tsx
(Vite)或 src/index.tsx
(CRA)中配置 Provider:
tsx
// src/main.tsx (Vite) 或 src/index.tsx (CRA)
import React from 'react'
import ReactDOM from 'react-dom/client'
import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { queryClient } from './lib/queryClient'
import App from './App'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
{/* 仅在开发环境显示开发工具 */}
{process.env.NODE_ENV === 'development' && (
<ReactQueryDevtools initialIsOpen={false} />
)}
</QueryClientProvider>
</React.StrictMode>
)
步骤 3:配置详细选项
如需更精细的控制,可以扩展 QueryClient 配置:
tsx
// src/lib/queryClient.ts
import { QueryClient } from '@tanstack/react-query'
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
// 数据缓存配置
staleTime: 5 * 60 * 1000, // 5分钟内数据被认为是新鲜的
gcTime: 30 * 60 * 1000, // 30分钟后清理未使用的数据
// 重试配置
retry: (failureCount, error) => {
// 对于 404 错误不重试
if (error.status === 404) return false
// 最多重试 3 次
return failureCount < 3
},
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
// 自动重新获取配置
refetchOnWindowFocus: true,
refetchOnReconnect: true,
refetchOnMount: true,
// 网络模式
networkMode: 'online', // 'online' | 'always' | 'offlineFirst'
},
mutations: {
retry: 1,
networkMode: 'online',
},
},
})
2.5 第一个查询示例
创建 API 函数
首先创建一个 API 服务文件 src/services/api.ts
:
tsx
// src/services/api.ts
export interface User {
id: number
name: string
email: string
username: string
}
export interface Post {
id: number
title: string
body: string
userId: number
}
// 模拟 API 调用
const API_BASE = 'https://jsonplaceholder.typicode.com'
export const fetchUsers = async (): Promise<User[]> => {
const response = await fetch(`${API_BASE}/users`)
if (!response.ok) {
throw new Error('获取用户列表失败')
}
return response.json()
}
export const fetchUser = async (userId: number): Promise<User> => {
const response = await fetch(`${API_BASE}/users/${userId}`)
if (!response.ok) {
throw new Error(`获取用户 ${userId} 失败`)
}
return response.json()
}
export const fetchPosts = async (): Promise<Post[]> => {
const response = await fetch(`${API_BASE}/posts`)
if (!response.ok) {
throw new Error('获取文章列表失败')
}
return response.json()
}
创建第一个组件
创建 src/components/UserList.tsx
组件:
tsx
// src/components/UserList.tsx
import { useQuery } from '@tanstack/react-query'
import { fetchUsers, User } from '../services/api'
export function UserList() {
const {
data: users,
isPending,
isError,
error
} = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 5 * 60 * 1000, // 5分钟内数据保持新鲜
})
if (isPending) {
return <div className="loading">正在加载用户列表...</div>
}
if (isError) {
return (
<div className="error">
<h3>加载失败</h3>
<p>{error.message}</p>
<button onClick={() => window.location.reload()}>
重试
</button>
</div>
)
}
return (
<div className="user-list">
<h2>用户列表</h2>
<div className="users">
{users?.map((user: User) => (
<div key={user.id} className="user-card">
<h3>{user.name}</h3>
<p>用户名: {user.username}</p>
<p>邮箱: {user.email}</p>
</div>
))}
</div>
</div>
)
}
在 App 组件中使用
更新 src/App.tsx
:
tsx
// src/App.tsx
import { UserList } from './components/UserList'
import './App.css'
function App() {
return (
<div className="App">
<header className="App-header">
<h1>React Query 示例应用</h1>
</header>
<main>
<UserList />
</main>
</div>
)
}
export default App
添加基本样式
在 src/App.css
中添加样式:
css
/* src/App.css */
.App {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.App-header {
text-align: center;
margin-bottom: 30px;
}
.loading, .error {
text-align: center;
padding: 20px;
margin: 20px 0;
}
.error {
background-color: #fee;
border: 1px solid #fcc;
border-radius: 4px;
color: #c33;
}
.error button {
background-color: #c33;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
}
.user-list h2 {
margin-bottom: 20px;
}
.users {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.user-card {
background: #f9f9f9;
padding: 20px;
border-radius: 8px;
border: 1px solid #e0e0e0;
}
.user-card h3 {
margin: 0 0 10px 0;
color: #333;
}
.user-card p {
margin: 5px 0;
color: #666;
}
运行应用
现在您可以启动应用查看效果:
bash
npm run dev # 如果使用 Vite
# 或
npm start # 如果使用 Create React App
打开浏览器访问 http://localhost:5173
(Vite)或 http://localhost:3000
(CRA),您将看到:
- 加载状态: 初始显示"正在加载用户列表..."
- 数据展示: 加载完成后显示用户卡片列表
- 错误处理: 如果网络失败,显示错误信息和重试按钮
- 开发工具: 按 F12 可以看到 React Query DevTools(如果已安装)
关键概念说明
这个示例展示了 React Query 的核心概念:
- queryKey :
['users']
- 唯一标识这个查询 - queryFn :
fetchUsers
- 实际的数据获取函数 - 自动缓存: 相同的查询键会复用缓存数据
- 加载状态 :
isPending
表示首次加载 - 错误处理 :
isError
和error
提供错误信息 - 类型安全 : TypeScript 自动推断
users
的类型为User[] | undefined
3 核心API变更
3.1 参数对象化
v5 版本统一采用对象参数格式,替代了之前的多重载形式:
tsx
// 旧版用法
useQuery('todos', fetchTodos, { staleTime: 5000 })
// v5 新用法
useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
staleTime: 5000
})
这一变更提高了类型安全性和代码一致性,所有核心钩子(useQuery、useInfiniteQuery、useMutation 等)均采用相同模式。
3.2 状态命名调整
状态命名更加精确,明确区分不同加载阶段:
tsx
// 旧版
{ isLoading, isSuccess, isError }
// v5 新版
{ isPending, isLoading, isSuccess, isError }
isPending
: 查询已开始但尚未有数据isLoading
: 首次加载数据(初始加载)isFetching
: 数据刷新中(背景刷新)
3 TypeScript 支持
3.1 类型推断优化
v5 大幅改进了类型推断能力,大多数情况下无需显式指定泛型:
tsx
// 自动推断 data 类型为 Group[] | undefined
const fetchGroups = () : Promise<Group[]> =>
axios.get('/groups').then((response) => response.data)
const { data } = useQuery({
queryKey: ['groups'],
queryFn: fetchGroups
})
3.2 类型窄化
通过状态判断自动窄化数据类型:
tsx
const { data, isSuccess } = useQuery({
queryKey: ['user'],
queryFn: fetchUser
})
if (isSuccess) {
// TypeScript 知道此时 data 一定存在
console.log(data.name) // 正确推断类型
}
3.3 全局错误类型配置
可通过模块增强定义全局错误类型:
tsx
import '@tanstack/react-query'
import type { AxiosError } from 'axios'
declare module '@tanstack/react-query' {
interface Register {
defaultError: AxiosError
}
}
// 错误类型自动推断为 AxiosError | null
const { error } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups })
4 核心功能示例
4.1 基础查询
tsx
const {
data,
isPending,
isError,
error
} = useQuery({
queryKey: ['todos', todoId], // 数组形式的查询键
queryFn: () => fetchTodo(todoId),
staleTime: 60 * 1000, // 数据保鲜时间 1 分钟
retry: 2 // 失败重试次数
})
if (isPending) return <div>加载中...</div>
if (isError) return <div>错误: {error.message}</div>
return <div>{data.title}</div>
4.2 数据转换
使用 select
选项转换查询结果:
tsx
const { data } = useQuery({
queryKey: ['user'],
queryFn: fetchUser,
select: (user) => ({
id: user.id,
fullName: `${user.firstName} ${user.lastName}`
})
})
// data 类型自动推断为 { id: number; fullName: string }
4.3 变更操作
使用 useMutation
处理数据更新:
tsx
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: updateTodo,
onSuccess: () => {
// 更新成功后使相关查询失效,触发重新获取
queryClient.invalidateQueries({ queryKey: ['todos'] })
}
})
mutation.mutate({ id: 1, title: '更新后的标题' })
if (mutation.isPending) return <button disabled>保存中...</button>
return (
<button onClick={() => mutation.mutate({ id: 1, title: '新标题' })}>
更新
</button>
)
4.4 查询选项复用
使用 queryOptions
helper 复用查询配置:
tsx
import { queryOptions } from '@tanstack/react-query'
function todoOptions(todoId: number) {
return queryOptions({
queryKey: ['todos', todoId],
queryFn: () => fetchTodo(todoId),
staleTime: 5 * 1000
})
}
// 在组件中使用
const { data } = useQuery(todoOptions(1))
// 在预获取中使用
queryClient.prefetchQuery(todoOptions(2))
4 高级查询功能
4.1 无限查询
使用 useInfiniteQuery 实现无限滚动或加载更多:
tsx
import { useInfiniteQuery } from '@tanstack/react-query'
function fetchProjects({ pageParam = 0 }) {
return fetch(`/api/projects?page=${pageParam}`).then(res => res.json())
}
function Projects() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['projects'],
queryFn: fetchProjects,
getNextPageParam: (lastPage) => lastPage.nextCursor,
initialPageParam: 0,
})
return (
<div>
{data?.pages.map((page, index) => (
<div key={index}>
{page.projects.map(project => (
<p key={project.id}>{project.name}</p>
))}
</div>
))}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage ? '加载中...' : '加载更多'}
</button>
</div>
)
}
4.2 依赖查询
实现查询之间的依赖关系:
tsx
function UserProfile({ userId }) {
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
})
// 依赖 user 数据的查询
const { data: posts } = useQuery({
queryKey: ['posts', user?.id],
queryFn: () => fetchPosts(user.id),
enabled: !!user, // 仅当 user 存在时执行
})
return <div>{/* 渲染用户和帖子 */}</div>
}
5 状态更新与缓存
5.1 乐观更新
在服务器确认前更新UI,提升用户体验:
tsx
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: updateTodo,
onMutate: async (newTodo) => {
// 取消当前查询
await queryClient.cancelQueries({ queryKey: ['todo', newTodo.id] })
// 保存当前数据
const previousTodo = queryClient.getQueryData(['todo', newTodo.id])
// 乐观更新
queryClient.setQueryData(['todo', newTodo.id], newTodo)
// 返回上下文
return { previousTodo }
},
onError: (err, newTodo, context) => {
// 发生错误时回滚
queryClient.setQueryData(
['todo', newTodo.id],
context.previousTodo
)
},
onSettled: (newTodo) => {
// 无论成功失败,重新验证
queryClient.invalidateQueries({ queryKey: ['todo', newTodo.id] })
},
})
5.2 查询失效与重新验证
手动控制查询缓存:
tsx
// 使单个查询失效
queryClient.invalidateQueries({ queryKey: ['todos', 1] })
// 使所有 todos 相关查询失效
queryClient.invalidateQueries({ queryKey: ['todos'] })
// 强制重新获取,忽略缓存
queryClient.invalidateQueries({ queryKey: ['todos'], force: true })
// 预获取数据
queryClient.prefetchQuery({
queryKey: ['todos', 2],
queryFn: () => fetchTodo(2),
})
6 性能优化
6.1 查询结果选择器
使用 select 优化重渲染:
tsx
const { data: todoCount } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
select: (todos) => todos.filter(todo => !todo.completed).length,
})
6.2 禁用自动重试
在表单提交等场景禁用自动重试:
tsx
useMutation({
mutationFn: submitForm,
retry: false, // 禁用重试
})
6.3 结构化查询键
使用数组形式的查询键提高缓存效率:
tsx
// 基础查询键
useQuery({ queryKey: ['todos'] })
// 带参数的查询键
useQuery({ queryKey: ['todos', { status: 'active', page: 1 }] })
// 嵌套资源查询键
useQuery({ queryKey: ['user', 1, 'posts'] })
7 最佳实践
7.1 自定义 Hooks 封装
将查询逻辑封装为自定义 Hooks:
tsx
// hooks/useTodos.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
export function useTodos() {
return useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
})
}
export function useAddTodo() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: addTodo,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
})
}
7.2 错误处理策略
全局错误处理配置:
tsx
// 创建客户端时配置全局错误处理
const queryClient = new QueryClient({
defaultOptions: {
queries: {
onError: (error) => {
console.error('查询错误:', error)
// 可以在这里显示全局错误提示
},
},
mutations: {
onError: (error) => {
console.error('变更错误:', error)
},
},
},
})
7.3 与 React Suspense 集成
使用 useSuspenseQuery 简化加载状态处理:
tsx
import { useSuspenseQuery } from '@tanstack/react-query'
function TodoDetails({ todoId }) {
const { data } = useSuspenseQuery({
queryKey: ['todo', todoId],
queryFn: () => fetchTodo(todoId),
})
return <div>{data.title}</div>
}
// 在父组件中使用 Suspense
function TodoPage({ todoId }) {
return (
<React.Suspense fallback={<div>加载中...</div>}>
<TodoDetails todoId={todoId} />
</React.Suspense>
)
}
8 常见问题与故障排除
8.1 安装相关问题
问题:安装后导入失败
bash
Error: Cannot find module '@tanstack/react-query'
解决方案:
- 确认是否正确安装:
bash
npm list @tanstack/react-query
- 清理缓存并重新安装:
bash
npm cache clean --force
rm -rf node_modules package-lock.json
npm install
- 检查 Node.js 版本是否符合要求(>= 16.0.0)
问题:TypeScript 类型错误
bash
Cannot find module '@tanstack/react-query' or its corresponding type declarations
解决方案:
确保安装了正确的版本和类型定义:
bash
npm install @tanstack/react-query@latest
npm install --save-dev @types/react @types/react-dom
8.2 配置相关问题
问题:Provider 未正确配置
bash
Error: useQuery must be used within a QueryClientProvider
解决方案:
确保在应用根部正确包装 QueryClientProvider
:
tsx
// ❌ 错误
function App() {
return <UserList /> // useQuery 在这里会失败
}
// ✅ 正确
function App() {
return (
<QueryClientProvider client={queryClient}>
<UserList />
</QueryClientProvider>
)
}
问题:开发工具不显示
解决方案:
- 确认已安装开发工具:
bash
npm install --save-dev @tanstack/react-query-devtools
- 正确导入和使用:
tsx
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
// 确保在 QueryClientProvider 内部
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
8.3 查询相关问题
问题:数据不会自动刷新
可能原因:
staleTime
设置过长- 禁用了自动重新获取
解决方案:
tsx
useQuery({
queryKey: ['data'],
queryFn: fetchData,
staleTime: 0, // 立即标记为过期
refetchOnWindowFocus: true, // 窗口聚焦时刷新
refetchOnReconnect: true, // 网络重连时刷新
})
问题:查询一直处于 pending 状态
可能原因:
queryFn
没有返回 Promise- 网络请求被阻塞
enabled
选项设置为 false
解决方案:
- 检查
queryFn
是否正确:
tsx
// ❌ 错误 - 没有返回 Promise
const fetchData = () => {
fetch('/api/data').then(res => res.json())
}
// ✅ 正确
const fetchData = () => {
return fetch('/api/data').then(res => res.json())
}
// ✅ 或使用 async/await
const fetchData = async () => {
const res = await fetch('/api/data')
return res.json()
}
- 检查
enabled
选项:
tsx
useQuery({
queryKey: ['data'],
queryFn: fetchData,
enabled: true, // 确保查询已启用
})
问题:错误处理不生效
解决方案:
确保在 queryFn
中正确抛出错误:
tsx
const fetchData = async () => {
const response = await fetch('/api/data')
// ❌ 错误 - 不会触发错误状态
if (!response.ok) {
console.error('请求失败')
return null
}
// ✅ 正确 - 会触发错误状态
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return response.json()
}
8.4 性能相关问题
问题:组件频繁重渲染
可能原因:
- 查询键包含不稳定的引用
- 没有使用
select
优化数据选择
解决方案:
- 使用稳定的查询键:
tsx
// ❌ 错误 - 每次渲染都会创建新对象
const { data } = useQuery({
queryKey: ['user', { id: userId, active: true }],
queryFn: fetchUser
})
// ✅ 正确 - 使用基本类型
const { data } = useQuery({
queryKey: ['user', userId, 'active'],
queryFn: fetchUser
})
- 使用
select
优化:
tsx
const { data } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
select: users => users.filter(user => user.active), // 只在数据变化时重新计算
})
问题:内存占用过高
解决方案:
调整缓存配置:
tsx
const queryClient = new QueryClient({
defaultOptions: {
queries: {
gcTime: 5 * 60 * 1000, // 5分钟后清理未使用的数据
staleTime: 1 * 60 * 1000, // 1分钟后标记为过期
},
},
})
8.5 网络相关问题
问题:离线时查询失败
解决方案:
配置网络模式:
tsx
useQuery({
queryKey: ['data'],
queryFn: fetchData,
networkMode: 'offlineFirst', // 离线优先模式
retry: (failureCount, error) => {
// 网络错误时不重试
if (error.name === 'NetworkError') return false
return failureCount < 3
}
})
问题:请求超时
解决方案:
在 queryFn
中添加超时控制:
tsx
const fetchWithTimeout = async (url: string, timeout = 10000) => {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), timeout)
try {
const response = await fetch(url, {
signal: controller.signal
})
clearTimeout(timeoutId)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return response.json()
} catch (error) {
clearTimeout(timeoutId)
throw error
}
}
8.6 调试技巧
启用详细日志
tsx
import { QueryClient } from '@tanstack/react-query'
const queryClient = new QueryClient({
logger: {
log: console.log,
warn: console.warn,
error: console.error,
},
})
使用浏览器开发工具
- React Query DevTools: 查看查询状态、缓存数据和网络请求
- Network 面板: 监控实际的网络请求
- Console 面板: 查看错误信息和调试日志
- React DevTools: 检查组件状态和 props
添加调试信息
tsx
const { data, error, isError } = useQuery({
queryKey: ['debug-query'],
queryFn: async () => {
console.log('查询开始执行')
const result = await fetchData()
console.log('查询结果:', result)
return result
},
onError: (error) => {
console.error('查询失败:', error)
},
onSuccess: (data) => {
console.log('查询成功:', data)
}
})
通过以上指南,您应该能够解决大部分在使用 @tanstack/react-query 过程中遇到的问题。如果问题仍然存在,建议查看官方文档或在 GitHub 仓库中提交 issue。