全面掌握 TanStack Query:现代 React 应用的数据管理利器
12月 24, 2025
什么是 TanStack Query?#
TanStack Query(原名 React Query)是一个强大的数据获取和状态管理库,专门用于处理服务器状态。它不是传统意义上的状态管理库(如 Redux 或 Zustand),而是专注于解决异步数据获取、缓存、同步和更新的问题。
核心优势#
🚀 自动缓存:智能缓存策略减少不必要的请求
🔄 后台同步:自动在后台更新过期数据
⚡ 性能优化:内置分页、无限加载、乐观更新
🛠 开发者体验:强大的开发工具和极简的 API
🔒 TypeScript 原生:完整的类型安全支持
安装与基础配置#
npm install @tanstack/react-query基础设置#
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
// 创建 QueryClient 实例
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5分钟
cacheTime: 10 * 60 * 1000, // 10分钟
},
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
{/* 开发工具 - 强烈推荐 */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}核心概念详解#
1. Query(查询) - 数据获取#
import { useQuery } from '@tanstack/react-query';
// 定义查询函数
const fetchUsers = async () => {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
};
function UsersList() {
// 使用 useQuery hook
const {
data: users,
error,
isLoading,
isError,
isSuccess,
refetch,
isFetching,
} = useQuery({
queryKey: ['users'], // 查询的唯一标识
queryFn: fetchUsers, // 查询函数
staleTime: 1000 * 60 * 5, // 数据保鲜时间(5分钟)
cacheTime: 1000 * 60 * 10, // 缓存时间(10分钟)
retry: 3, // 失败重试次数
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
});
if (isLoading) return <div>加载中...</div>;
if (isError) return <div>错误: {error.message}</div>;
return (
<div>
<button onClick={() => refetch()} disabled={isFetching}>
{isFetching ? '刷新中...' : '刷新数据'}
</button>
{users.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}2. Mutation(变更) - 数据修改#
import { useMutation, useQueryClient } from '@tanstack/react-query';
const createUser = async (userData) => {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData),
});
if (!response.ok) throw new Error('创建用户失败');
return response.json();
};
function CreateUserForm() {
const queryClient = useQueryClient();
const [formData, setFormData] = useState({ name: '', email: '' });
const mutation = useMutation({
mutationFn: createUser,
onSuccess: (newUser) => {
// 1. 使相关查询失效,触发重新获取
queryClient.invalidateQueries({ queryKey: ['users'] });
// 2. 或者直接更新缓存(更高效)
queryClient.setQueryData(['users'], old => [...old, newUser]);
// 重置表单
setFormData({ name: '', email: '' });
},
onError: (error) => {
console.error('创建失败:', error);
},
onSettled: () => {
// 无论成功失败都会执行
console.log('Mutation 完成');
},
});
const handleSubmit = (e) => {
e.preventDefault();
mutation.mutate(formData);
};
return (
<form onSubmit={handleSubmit}>
<input
value={formData.name}
onChange={e => setFormData({...formData, name: e.target.value})}
placeholder="姓名"
/>
<input
value={formData.email}
onChange={e => setFormData({...formData, email: e.target.value})}
placeholder="邮箱"
/>
<button type="submit" disabled={mutation.isLoading}>
{mutation.isLoading ? '创建中...' : '创建用户'}
</button>
</form>
);
}高级特性实战#
1. 依赖查询(串行查询)#
function UserProfile({ userId }) {
// 先获取用户基本信息
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
// 依赖用户信息获取用户详情
const { data: profile } = useQuery({
queryKey: ['user-profile', userId],
queryFn: () => fetchUserProfile(userId),
enabled: !!user, // 只有用户信息存在时才执行
});
// 获取用户帖子
const { data: posts } = useQuery({
queryKey: ['user-posts', userId],
queryFn: () => fetchUserPosts(userId),
enabled: !!user, // 依赖用户信息
});
if (!user) return <div>加载用户信息...</div>;
return (
<div>
<h1>{user.name}</h1>
{profile && <p>{profile.bio}</p>}
{posts && posts.map(post => <div key={post.id}>{post.title}</div>)}
</div>
);
}2. 乐观更新 - 提升用户体验#
function OptimisticTodoList() {
const queryClient = useQueryClient();
const updateTodo = useMutation({
mutationFn: (updatedTodo) =>
fetch(`/api/todos/${updatedTodo.id}`, {
method: 'PUT',
body: JSON.stringify(updatedTodo),
}),
// 乐观更新:立即更新UI,如果失败则回滚
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: (error, updatedTodo, context) => {
// 出错时回滚到之前的状态
queryClient.setQueryData(['todos'], context.previousTodos);
},
onSettled: () => {
// 最终重新获取确保数据一致
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
const toggleTodo = (todo) => {
updateTodo.mutate({
...todo,
completed: !todo.completed,
});
};
// ... 组件渲染逻辑
}3. 无限滚动与分页#
import { useInfiniteQuery } from '@tanstack/react-query';
const fetchPosts = async ({ pageParam = 1 }) => {
const response = await fetch(`/api/posts?page=${pageParam}&limit=10`);
return response.json();
};
function InfinitePosts() {
const {
data,
error,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
status,
} = useInfiniteQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
getNextPageParam: (lastPage) => lastPage.nextPage,
getPreviousPageParam: (firstPage) => firstPage.prevPage,
});
if (status === 'loading') return <div>加载中...</div>;
if (status === 'error') return <div>错误: {error.message}</div>;
return (
<div>
{data.pages.map((page, pageIndex) => (
<div key={pageIndex}>
{page.posts.map(post => (
<article key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</article>
))}
</div>
))}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage ? '加载更多...' : '加载更多'}
</button>
</div>
);
}实际项目最佳实践#
1. 自定义 Hook 封装#
// hooks/useUsers.js
export const useUsers = () => {
return useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 5 * 60 * 1000,
});
};
export const useUser = (userId) => {
return useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
enabled: !!userId, // 只有 userId 存在时才查询
});
};
export const useCreateUser = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
};
// 在组件中使用
function UserManagement() {
const { data: users } = useUsers();
const createUserMutation = useCreateUser();
// ... 组件逻辑
}2. 错误处理与加载状态统一管理#
// components/QueryBoundary.jsx
function QueryBoundary({ children, loading, error, loadingComponent, errorComponent }) {
if (loading) return loadingComponent || <DefaultLoading />;
if (error) return errorComponent || <DefaultError error={error} />;
return children;
}
// components/DefaultError.jsx
function DefaultError({ error, onRetry }) {
return (
<div className="error-boundary">
<h3>出错了</h3>
<p>{error.message}</p>
<button onClick={onRetry}>重试</button>
</div>
);
}
// 使用示例
function UserProfile({ userId }) {
const { data, isLoading, error, refetch } = useUser(userId);
return (
<QueryBoundary
loading={isLoading}
error={error}
loadingComponent={<UserProfileSkeleton />}
errorComponent={<DefaultError error={error} onRetry={refetch} />}
>
<UserProfileContent user={data} />
</QueryBoundary>
);
}3. Next.js 集成(服务端渲染)#
// pages/users.js
import { dehydrate, QueryClient } from '@tanstack/react-query';
export async function getServerSideProps() {
const queryClient = new QueryClient();
// 服务端预取数据
await queryClient.prefetchQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
return {
props: {
dehydratedState: dehydrate(queryClient),
},
};
}
function UsersPage({ dehydratedState }) {
// 客户端会直接使用服务端预取的数据
const { data: users } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
return (
<div>
{users.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}性能优化技巧#
1. 查询去重与缓存优化#
const queryClient = new QueryClient({
defaultOptions: {
queries: {
// 相同查询在5秒内不会重复发起
staleTime: 5000,
// 缓存数据保留10分钟
cacheTime: 600000,
// 窗口失焦时重新获取数据
refetchOnWindowFocus: true,
// 网络重连时重新获取
refetchOnReconnect: true,
// 页面不可见时暂停查询
refetchOnMount: true,
},
},
});2. 选择性数据更新#
// 只更新特定数据,而不是整个列表
queryClient.setQueryData(['todos'], oldTodos =>
oldTodos.map(todo =>
todo.id === updatedTodo.id ? updatedTodo : todo
)
);
// 或者只使特定查询失效
queryClient.invalidateQueries({
queryKey: ['todos'],
predicate: query =>
query.queryKey[0] === 'todos' && query.data.some(todo => todo.important)
});常见问题与解决方案#
1. 循环依赖问题#
// ❌ 错误:在渲染中立即设置查询依赖
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
enabled: userId !== undefined, // 可能导致循环渲染
});
// ✅ 正确:使用稳定的依赖
const [userId, setUserId] = useState(undefined);
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
enabled: !!userId, // 明确的布尔值
});2. 内存泄漏处理#
// 自动垃圾回收
const queryClient = new QueryClient({
defaultOptions: {
queries: {
cacheTime: 10 * 60 * 1000, // 10分钟后清理未使用的缓存
staleTime: 5 * 60 * 1000, // 5分钟后标记为过期
},
},
});总结#
TanStack Query 是现代 React 应用中处理服务器状态的终极解决方案。通过智能缓存、自动同步和极简的 API,它极大地简化了数据管理复杂度。掌握 TanStack Query 不仅能提升开发效率,还能显著改善应用性能和用户体验。
关键收获:
使用
useQuery处理数据获取,useMutation处理数据修改合理配置缓存策略平衡性能与数据新鲜度
利用乐观更新提升用户体验
封装自定义 Hook 提高代码复用性
结合错误边界提供完善的错误处理
开始在你的项目中实践这些模式,你会发现数据管理变得前所未有的简单和高效!
本文代码示例基于 TanStack Query v4+,建议查看官方文档获取最新 API 变更。