12. 请求缓存 - React Query / SWR概述React Query 和 SWR 是专为 React 设计的服务端状态管理库它们自动处理数据获取、缓存、重新验证、后台更新等复杂逻辑让开发者专注于业务而非请求状态管理。维度内容What管理服务端状态的库自动处理缓存、重试、后台更新Why减少重复请求自动处理加载/错误状态提升用户体验When需要缓存、重试、后台更新的数据获取场景Where数据获取组件中替代手动 fetch/axios useState/useEffectWho需要优化数据获取的开发者Howconst { data } useQuery([key], fetcher)或useSWR(key, fetcher)1. 为什么需要请求缓存库1.1 传统数据获取的问题// ❌ 传统方式手动管理所有状态 function TodoList() { const [todos, setTodos] useState([]); const [loading, setLoading] useState(true); const [error, setError] useState(null); useEffect(() { fetchTodos(); }, []); async function fetchTodos() { try { setLoading(true); const response await fetch(/api/todos); const data await response.json(); setTodos(data); } catch (err) { setError(err); } finally { setLoading(false); } } // 还需要处理 // - 缓存 // - 重新验证 // - 后台更新 // - 并发请求去重 // - 分页/无限加载 // - 乐观更新 // ... 代码会越来越复杂 }1.2 React Query / SWR 解决方案// ✅ 使用 React Query import { useQuery } from tanstack/react-query; function TodoList() { const { data: todos, isLoading, error } useQuery({ queryKey: [todos], queryFn: () fetch(/api/todos).then(res res.json()) }); // 自动处理 // ✅ 加载和错误状态 // ✅ 缓存 // ✅ 后台重新验证 // ✅ 请求去重 // ✅ 窗口焦点重新获取 // ✅ 分页/无限加载 // ✅ 乐观更新 if (isLoading) return div加载中.../div; if (error) return div错误: {error.message}/div; return TodoItems todos{todos} /; }2. React Query (TanStack Query) 详解2.1 安装与配置npminstalltanstack/react-query// main.jsx import { QueryClient, QueryClientProvider } from tanstack/react-query; import { ReactQueryDevtools } from tanstack/react-query-devtools; const queryClient new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5, // 5分钟内数据被认为是新鲜的 gcTime: 1000 * 60 * 10, // 缓存保留10分钟 retry: 3, // 失败重试3次 retryDelay: 1000, // 重试延迟1秒 refetchOnWindowFocus: true, // 窗口焦点时重新获取 refetchOnReconnect: true, // 网络重连时重新获取 }, }, }); function App() { return ( QueryClientProvider client{queryClient} YourApp / ReactQueryDevtools initialIsOpen{false} / /QueryClientProvider ); }2.2 基础 useQueryimport { useQuery } from tanstack/react-query; // 获取用户列表 function Users() { const { data, isLoading, error, isError, refetch, // 手动重新获取 dataUpdatedAt // 最后更新时间 } useQuery({ queryKey: [users], queryFn: async () { const response await fetch(https://jsonplaceholder.typicode.com/users); if (!response.ok) throw new Error(获取失败); return response.json(); }, }); if (isLoading) return Spinner /; if (isError) return Error message{error.message} /; return ( div button onClick{() refetch()}刷新/button ul {data.map(user ( li key{user.id}{user.name}/li ))} /ul /div ); }2.3 带参数的查询function UserDetail({ userId }) { const { data: user, isLoading } useQuery({ queryKey: [user, userId], queryFn: async () { const response await fetch(/api/users/${userId}); return response.json(); }, enabled: !!userId, // userId 存在时才执行 }); if (!userId) return div请选择用户/div; if (isLoading) return Spinner /; return div{user?.name}/div; }2.4 依赖查询function DependentQueries({ userId }) { // 第一个查询获取用户信息 const { data: user } useQuery({ queryKey: [user, userId], queryFn: () fetchUser(userId), enabled: !!userId, }); // 第二个查询依赖第一个查询的结果 const { data: posts } useQuery({ queryKey: [user-posts, user?.id], queryFn: () fetchUserPosts(user.id), enabled: !!user?.id, // 等待 user 加载完成 }); return div{/* 渲染内容 */}/div; }2.5 并行查询function ParallelQueries() { // 方式1多个 useQuery 调用 const usersQuery useQuery({ queryKey: [users], queryFn: fetchUsers }); const postsQuery useQuery({ queryKey: [posts], queryFn: fetchPosts }); const commentsQuery useQuery({ queryKey: [comments], queryFn: fetchComments }); if (usersQuery.isLoading || postsQuery.isLoading || commentsQuery.isLoading) { return Spinner /; } return div{/* 渲染所有数据 */}/div; } // 方式2useQueries动态数量的查询 function DynamicParallelQueries({ ids }) { const userQueries useQueries({ queries: ids.map(id ({ queryKey: [user, id], queryFn: () fetchUser(id), })), }); const isLoading userQueries.some(query query.isLoading); if (isLoading) return Spinner /; return ( div {userQueries.map(({ data }, index) ( div key{index}{data?.name}/div ))} /div ); }2.6 分页查询import { useQuery } from tanstack/react-query; function PaginatedList() { const [page, setPage] useState(1); const [totalPages, setTotalPages] useState(0); const { data, isLoading, isFetching } useQuery({ queryKey: [items, page], queryFn: async () { const response await fetch(/api/items?page${page}limit10); const result await response.json(); setTotalPages(result.totalPages); return result.data; }, keepPreviousData: true, // 保留旧数据避免加载状态闪烁 }); return ( div {isLoading ? ( Spinner / ) : ( ItemList items{data} / {isFetching div加载中.../div} Pagination page{page} totalPages{totalPages} onPageChange{setPage} / / )} /div ); }2.7 无限加载import { useInfiniteQuery } from tanstack/react-query; function InfiniteScroll() { const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, } useInfiniteQuery({ queryKey: [items], queryFn: async ({ pageParam 1 }) { const response await fetch(/api/items?page${pageParam}limit20); return response.json(); }, getNextPageParam: (lastPage, pages) { // 返回下一页的页码如果没有下一页返回 undefined return lastPage.hasNext ? lastPage.page 1 : undefined; }, initialPageParam: 1, }); if (isLoading) return Spinner /; return ( div {data.pages.map((page, i) ( div key{i} {page.items.map(item ( div key{item.id}{item.name}/div ))} /div ))} {hasNextPage ( button onClick{() fetchNextPage()} disabled{isFetchingNextPage} {isFetchingNextPage ? 加载更多... : 加载更多} /button )} /div ); }2.8 数据变更 - useMutationimport { useMutation, useQueryClient } from tanstack/react-query; function CreatePost() { const queryClient useQueryClient(); const mutation useMutation({ mutationFn: (newPost) { return fetch(/api/posts, { method: POST, body: JSON.stringify(newPost), headers: { Content-Type: application/json }, }).then(res res.json()); }, onSuccess: (data) { // 成功后的操作 console.log(创建成功:, data); // 方式1使缓存失效重新获取 queryClient.invalidateQueries({ queryKey: [posts] }); // 方式2直接更新缓存 queryClient.setQueryData([posts], (old) [...old, data]); }, onError: (error) { console.error(创建失败:, error); }, }); return ( button onClick{() mutation.mutate({ title: 新文章 })} {mutation.isPending ? 创建中... : 创建文章} /button ); }2.9 乐观更新function OptimisticUpdate() { const queryClient useQueryClient(); const mutation useMutation({ mutationFn: (newTodo) { return fetch(/api/todos, { method: POST, body: JSON.stringify(newTodo), }).then(res res.json()); }, 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] }); }, }); return button onClick{() mutation.mutate({ title: 新任务 })} 添加任务 /button; }3. SWR 详解3.1 安装与配置npminstallswr// 全局配置 import { SWRConfig } from swr; function App() { return ( SWRConfig value{{ fetcher: (url) fetch(url).then(res res.json()), revalidateOnFocus: true, revalidateOnReconnect: true, refreshInterval: 0, errorRetryCount: 3, }} YourApp / /SWRConfig ); }3.2 基础 useSWRimport useSWR from swr; // 全局 fetcher const fetcher (url) fetch(url).then(res res.json()); function Users() { const { data, error, isLoading, isValidating, mutate } useSWR( https://jsonplaceholder.typicode.com/users, fetcher ); if (isLoading) return Spinner /; if (error) return Error message{error.message} /; return ( div button onClick{() mutate()}刷新/button ul {data.map(user ( li key{user.id}{user.name}/li ))} /ul /div ); }3.3 带参数的请求function UserDetail({ userId }) { const { data, isLoading } useSWR( userId ? /api/users/${userId} : null, // null 时不请求 fetcher ); if (!userId) return div请选择用户/div; if (isLoading) return Spinner /; return div{data?.name}/div; }3.4 依赖请求function DependentSWR({ userId }) { // 获取用户信息 const { data: user } useSWR( userId ? /api/users/${userId} : null, fetcher ); // 依赖用户信息获取文章 const { data: posts } useSWR( user ? /api/users/${user.id}/posts : null, fetcher ); return div{/* 渲染 */}/div; }3.5 分页function PaginatedSWR() { const [page, setPage] useState(1); const { data, isLoading } useSWR( /api/items?page${page}limit10, fetcher, { keepPreviousData: true, // 保留旧数据 } ); return ( div ItemList items{data?.items} / Pagination page{page} onPageChange{setPage} / /div ); }3.6 无限加载import useSWRInfinite from swr/infinite; function InfiniteSWR() { const getKey (pageIndex, previousPageData) { // 到达最后一页 if (previousPageData !previousPageData.hasNext) return null; // 第一页 page1 return /api/items?page${pageIndex 1}limit20; }; const { data, size, setSize, isLoading } useSWRInfinite(getKey, fetcher); const items data ? data.flatMap(page page.items) : []; const isLoadingMore isLoading || (size 0 data typeof data[size - 1] undefined); const isEmpty data?.[0]?.items?.length 0; const isReachingEnd isEmpty || (data data[data.length - 1]?.items?.length 20); return ( div {items.map(item ( div key{item.id}{item.name}/div ))} {!isReachingEnd ( button onClick{() setSize(size 1)} disabled{isLoadingMore} {isLoadingMore ? 加载中... : 加载更多} /button )} /div ); }3.7 数据变更import useSWR, { mutate } from swr; function CreatePostSWR() { const { data: posts, mutate: mutatePosts } useSWR(/api/posts, fetcher); const createPost async (newPost) { // 乐观更新 const optimisticPosts [...posts, newPost]; mutatePosts(optimisticPosts, false); // 不重新验证 try { const response await fetch(/api/posts, { method: POST, body: JSON.stringify(newPost), }); const data await response.json(); // 使用服务器返回的数据更新 mutatePosts([...posts, data]); } catch (error) { // 发生错误时回滚 mutatePosts(posts); console.error(error); } }; return button onClick{() createPost({ title: 新文章 })} 创建文章 /button; } // 全局 mutate function UpdateUser() { const updateUser async (userId, userData) { // 更新特定 key 的数据 mutate(/api/users/${userId}, async () { const response await fetch(/api/users/${userId}, { method: PUT, body: JSON.stringify(userData), }); return response.json(); }); }; return button onClick{() updateUser(1, { name: New Name })} 更新用户 /button; }4. React Query vs SWR 对比4.1 特性对比特性React QuerySWR学习曲线中等平缓API 设计功能丰富选项多简洁易上手DevTools优秀的开发者工具基础版TypeScript极好极好体积~13KB~5KB无限查询useInfiniteQueryuseSWRInfinite并行查询useQueries多个 useSWR依赖查询enabled 选项key 为 null乐观更新内置支持需要手动实现GC 时间可配置 gcTime无内置 GCReact 19 支持完全支持完全支持4.2 选择建议// 选择 React Query 如果 // - 需要复杂的数据同步逻辑 // - 需要强大的开发者工具 // - 需要细粒度的缓存控制 // - 项目已经使用 TanStack 生态 // 选择 SWR 如果 // - 追求简洁和轻量 // - 只需要基础的缓存和重新验证 // - 项目使用 Next.jsSWR 由 Vercel 开发 // - 对包体积有严格要求5. 高级模式5.1 预取数据// React Query const queryClient useQueryClient(); // 鼠标悬停时预取 function UserLink({ userId }) { const prefetchUser () { queryClient.prefetchQuery({ queryKey: [user, userId], queryFn: () fetchUser(userId), }); }; return ( Link to{/users/${userId}} onMouseEnter{prefetchUser} 查看用户 /Link ); }5.2 SSR/SSG 数据获取// Next.js SWR function Profile({ fallbackData }) { const { data } useSWR(/api/user, fetcher, { fallbackData }); return div{data.name}/div; } // React Query Next.js import { dehydrate, HydrationBoundary } from tanstack/react-query; export async function getServerSideProps() { const queryClient new QueryClient(); await queryClient.prefetchQuery({ queryKey: [user], queryFn: fetchUser, }); return { props: { dehydratedState: dehydrate(queryClient), }, }; }5.3 轮询 / 实时数据// React Query 轮询 const { data } useQuery({ queryKey: [realtime-data], queryFn: fetchData, refetchInterval: 5000, // 每5秒重新获取 refetchIntervalInBackground: true, // 后台也轮询 }); // SWR 轮询 const { data } useSWR(/api/realtime, fetcher, { refreshInterval: 5000, refreshWhenHidden: true, });6. 性能优化技巧6.1 缓存策略// React Query 缓存策略 const queryClient new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, // 5分钟内不重新获取 gcTime: 10 * 60 * 1000, // 10分钟后垃圾回收 refetchOnWindowFocus: false, // 不自动重新获取 }, }, }); // 针对特定查询 const { data } useQuery({ queryKey: [static-data], queryFn: fetchStaticData, staleTime: Infinity, // 永不过期 gcTime: Infinity, // 永不清理 });7. 完整示例任务管理应用import { useQuery, useMutation, useQueryClient } from tanstack/react-query; import { useState } from react; // API 函数 const api { fetchTasks: () fetch(/api/tasks).then(res res.json()), createTask: (task) fetch(/api/tasks, { method: POST, body: JSON.stringify(task), headers: { Content-Type: application/json }, }).then(res res.json()), updateTask: ({ id, updates }) fetch(/api/tasks/${id}, { method: PATCH, body: JSON.stringify(updates), headers: { Content-Type: application/json }, }).then(res res.json()), deleteTask: (id) fetch(/api/tasks/${id}, { method: DELETE }), }; function TaskManager() { const [filter, setFilter] useState(all); const queryClient useQueryClient(); // 获取任务列表 const { data: tasks [], isLoading } useQuery({ queryKey: [tasks], queryFn: api.fetchTasks, }); // 创建任务 const createMutation useMutation({ mutationFn: api.createTask, onSuccess: () { queryClient.invalidateQueries({ queryKey: [tasks] }); }, }); // 更新任务 const updateMutation useMutation({ mutationFn: api.updateTask, onMutate: async ({ id, updates }) { await queryClient.cancelQueries({ queryKey: [tasks] }); const previousTasks queryClient.getQueryData([tasks]); queryClient.setQueryData([tasks], (old) old.map(task task.id id ? { ...task, ...updates } : task) ); return { previousTasks }; }, onError: (err, variables, context) { queryClient.setQueryData([tasks], context.previousTasks); }, onSettled: () { queryClient.invalidateQueries({ queryKey: [tasks] }); }, }); // 删除任务 const deleteMutation useMutation({ mutationFn: api.deleteTask, onSuccess: () { queryClient.invalidateQueries({ queryKey: [tasks] }); }, }); const filteredTasks tasks.filter(task { if (filter active) return !task.completed; if (filter completed) return task.completed; return true; }); const handleAddTask (title) { createMutation.mutate({ title, completed: false }); }; const handleToggleTask (id, completed) { updateMutation.mutate({ id, updates: { completed: !completed } }); }; const handleDeleteTask (id) { deleteMutation.mutate(id); }; if (isLoading) return div加载中.../div; return ( div h1任务管理/h1 AddTaskForm onAdd{handleAddTask} / div button onClick{() setFilter(all)}全部/button button onClick{() setFilter(active)}未完成/button button onClick{() setFilter(completed)}已完成/button /div TaskList tasks{filteredTasks} onToggle{handleToggleTask} onDelete{handleDeleteTask} / {createMutation.isPending div添加中.../div} /div ); } function AddTaskForm({ onAdd }) { const [title, setTitle] useState(); const handleSubmit (e) { e.preventDefault(); if (title.trim()) { onAdd(title); setTitle(); } }; return ( form onSubmit{handleSubmit} input typetext value{title} onChange{(e) setTitle(e.target.value)} placeholder添加新任务... / button typesubmit添加/button /form ); } function TaskList({ tasks, onToggle, onDelete }) { return ( ul {tasks.map(task ( li key{task.id} input typecheckbox checked{task.completed} onChange{() onToggle(task.id, task.completed)} / span style{{ textDecoration: task.completed ? line-through : none }} {task.title} /span button onClick{() onDelete(task.id)}删除/button /li ))} /ul ); } export default TaskManager;8. 总结核心要点要点说明核心价值自动管理服务端状态减少样板代码主要特性缓存、重试、后台更新、乐观更新React Query功能全面适合复杂场景SWR轻量简洁适合中小项目记忆口诀服务状态管理难React Query SWR 来帮忙缓存重试自动做加载错误不用忙乐观更新体验好后台刷新数据强9. 相关资源React Query 官方文档SWR 官方文档React Query DevTools
04-性能优化与最佳实践——12. 请求缓存 - React Query / SWR
12. 请求缓存 - React Query / SWR概述React Query 和 SWR 是专为 React 设计的服务端状态管理库它们自动处理数据获取、缓存、重新验证、后台更新等复杂逻辑让开发者专注于业务而非请求状态管理。维度内容What管理服务端状态的库自动处理缓存、重试、后台更新Why减少重复请求自动处理加载/错误状态提升用户体验When需要缓存、重试、后台更新的数据获取场景Where数据获取组件中替代手动 fetch/axios useState/useEffectWho需要优化数据获取的开发者Howconst { data } useQuery([key], fetcher)或useSWR(key, fetcher)1. 为什么需要请求缓存库1.1 传统数据获取的问题// ❌ 传统方式手动管理所有状态 function TodoList() { const [todos, setTodos] useState([]); const [loading, setLoading] useState(true); const [error, setError] useState(null); useEffect(() { fetchTodos(); }, []); async function fetchTodos() { try { setLoading(true); const response await fetch(/api/todos); const data await response.json(); setTodos(data); } catch (err) { setError(err); } finally { setLoading(false); } } // 还需要处理 // - 缓存 // - 重新验证 // - 后台更新 // - 并发请求去重 // - 分页/无限加载 // - 乐观更新 // ... 代码会越来越复杂 }1.2 React Query / SWR 解决方案// ✅ 使用 React Query import { useQuery } from tanstack/react-query; function TodoList() { const { data: todos, isLoading, error } useQuery({ queryKey: [todos], queryFn: () fetch(/api/todos).then(res res.json()) }); // 自动处理 // ✅ 加载和错误状态 // ✅ 缓存 // ✅ 后台重新验证 // ✅ 请求去重 // ✅ 窗口焦点重新获取 // ✅ 分页/无限加载 // ✅ 乐观更新 if (isLoading) return div加载中.../div; if (error) return div错误: {error.message}/div; return TodoItems todos{todos} /; }2. React Query (TanStack Query) 详解2.1 安装与配置npminstalltanstack/react-query// main.jsx import { QueryClient, QueryClientProvider } from tanstack/react-query; import { ReactQueryDevtools } from tanstack/react-query-devtools; const queryClient new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5, // 5分钟内数据被认为是新鲜的 gcTime: 1000 * 60 * 10, // 缓存保留10分钟 retry: 3, // 失败重试3次 retryDelay: 1000, // 重试延迟1秒 refetchOnWindowFocus: true, // 窗口焦点时重新获取 refetchOnReconnect: true, // 网络重连时重新获取 }, }, }); function App() { return ( QueryClientProvider client{queryClient} YourApp / ReactQueryDevtools initialIsOpen{false} / /QueryClientProvider ); }2.2 基础 useQueryimport { useQuery } from tanstack/react-query; // 获取用户列表 function Users() { const { data, isLoading, error, isError, refetch, // 手动重新获取 dataUpdatedAt // 最后更新时间 } useQuery({ queryKey: [users], queryFn: async () { const response await fetch(https://jsonplaceholder.typicode.com/users); if (!response.ok) throw new Error(获取失败); return response.json(); }, }); if (isLoading) return Spinner /; if (isError) return Error message{error.message} /; return ( div button onClick{() refetch()}刷新/button ul {data.map(user ( li key{user.id}{user.name}/li ))} /ul /div ); }2.3 带参数的查询function UserDetail({ userId }) { const { data: user, isLoading } useQuery({ queryKey: [user, userId], queryFn: async () { const response await fetch(/api/users/${userId}); return response.json(); }, enabled: !!userId, // userId 存在时才执行 }); if (!userId) return div请选择用户/div; if (isLoading) return Spinner /; return div{user?.name}/div; }2.4 依赖查询function DependentQueries({ userId }) { // 第一个查询获取用户信息 const { data: user } useQuery({ queryKey: [user, userId], queryFn: () fetchUser(userId), enabled: !!userId, }); // 第二个查询依赖第一个查询的结果 const { data: posts } useQuery({ queryKey: [user-posts, user?.id], queryFn: () fetchUserPosts(user.id), enabled: !!user?.id, // 等待 user 加载完成 }); return div{/* 渲染内容 */}/div; }2.5 并行查询function ParallelQueries() { // 方式1多个 useQuery 调用 const usersQuery useQuery({ queryKey: [users], queryFn: fetchUsers }); const postsQuery useQuery({ queryKey: [posts], queryFn: fetchPosts }); const commentsQuery useQuery({ queryKey: [comments], queryFn: fetchComments }); if (usersQuery.isLoading || postsQuery.isLoading || commentsQuery.isLoading) { return Spinner /; } return div{/* 渲染所有数据 */}/div; } // 方式2useQueries动态数量的查询 function DynamicParallelQueries({ ids }) { const userQueries useQueries({ queries: ids.map(id ({ queryKey: [user, id], queryFn: () fetchUser(id), })), }); const isLoading userQueries.some(query query.isLoading); if (isLoading) return Spinner /; return ( div {userQueries.map(({ data }, index) ( div key{index}{data?.name}/div ))} /div ); }2.6 分页查询import { useQuery } from tanstack/react-query; function PaginatedList() { const [page, setPage] useState(1); const [totalPages, setTotalPages] useState(0); const { data, isLoading, isFetching } useQuery({ queryKey: [items, page], queryFn: async () { const response await fetch(/api/items?page${page}limit10); const result await response.json(); setTotalPages(result.totalPages); return result.data; }, keepPreviousData: true, // 保留旧数据避免加载状态闪烁 }); return ( div {isLoading ? ( Spinner / ) : ( ItemList items{data} / {isFetching div加载中.../div} Pagination page{page} totalPages{totalPages} onPageChange{setPage} / / )} /div ); }2.7 无限加载import { useInfiniteQuery } from tanstack/react-query; function InfiniteScroll() { const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, } useInfiniteQuery({ queryKey: [items], queryFn: async ({ pageParam 1 }) { const response await fetch(/api/items?page${pageParam}limit20); return response.json(); }, getNextPageParam: (lastPage, pages) { // 返回下一页的页码如果没有下一页返回 undefined return lastPage.hasNext ? lastPage.page 1 : undefined; }, initialPageParam: 1, }); if (isLoading) return Spinner /; return ( div {data.pages.map((page, i) ( div key{i} {page.items.map(item ( div key{item.id}{item.name}/div ))} /div ))} {hasNextPage ( button onClick{() fetchNextPage()} disabled{isFetchingNextPage} {isFetchingNextPage ? 加载更多... : 加载更多} /button )} /div ); }2.8 数据变更 - useMutationimport { useMutation, useQueryClient } from tanstack/react-query; function CreatePost() { const queryClient useQueryClient(); const mutation useMutation({ mutationFn: (newPost) { return fetch(/api/posts, { method: POST, body: JSON.stringify(newPost), headers: { Content-Type: application/json }, }).then(res res.json()); }, onSuccess: (data) { // 成功后的操作 console.log(创建成功:, data); // 方式1使缓存失效重新获取 queryClient.invalidateQueries({ queryKey: [posts] }); // 方式2直接更新缓存 queryClient.setQueryData([posts], (old) [...old, data]); }, onError: (error) { console.error(创建失败:, error); }, }); return ( button onClick{() mutation.mutate({ title: 新文章 })} {mutation.isPending ? 创建中... : 创建文章} /button ); }2.9 乐观更新function OptimisticUpdate() { const queryClient useQueryClient(); const mutation useMutation({ mutationFn: (newTodo) { return fetch(/api/todos, { method: POST, body: JSON.stringify(newTodo), }).then(res res.json()); }, 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] }); }, }); return button onClick{() mutation.mutate({ title: 新任务 })} 添加任务 /button; }3. SWR 详解3.1 安装与配置npminstallswr// 全局配置 import { SWRConfig } from swr; function App() { return ( SWRConfig value{{ fetcher: (url) fetch(url).then(res res.json()), revalidateOnFocus: true, revalidateOnReconnect: true, refreshInterval: 0, errorRetryCount: 3, }} YourApp / /SWRConfig ); }3.2 基础 useSWRimport useSWR from swr; // 全局 fetcher const fetcher (url) fetch(url).then(res res.json()); function Users() { const { data, error, isLoading, isValidating, mutate } useSWR( https://jsonplaceholder.typicode.com/users, fetcher ); if (isLoading) return Spinner /; if (error) return Error message{error.message} /; return ( div button onClick{() mutate()}刷新/button ul {data.map(user ( li key{user.id}{user.name}/li ))} /ul /div ); }3.3 带参数的请求function UserDetail({ userId }) { const { data, isLoading } useSWR( userId ? /api/users/${userId} : null, // null 时不请求 fetcher ); if (!userId) return div请选择用户/div; if (isLoading) return Spinner /; return div{data?.name}/div; }3.4 依赖请求function DependentSWR({ userId }) { // 获取用户信息 const { data: user } useSWR( userId ? /api/users/${userId} : null, fetcher ); // 依赖用户信息获取文章 const { data: posts } useSWR( user ? /api/users/${user.id}/posts : null, fetcher ); return div{/* 渲染 */}/div; }3.5 分页function PaginatedSWR() { const [page, setPage] useState(1); const { data, isLoading } useSWR( /api/items?page${page}limit10, fetcher, { keepPreviousData: true, // 保留旧数据 } ); return ( div ItemList items{data?.items} / Pagination page{page} onPageChange{setPage} / /div ); }3.6 无限加载import useSWRInfinite from swr/infinite; function InfiniteSWR() { const getKey (pageIndex, previousPageData) { // 到达最后一页 if (previousPageData !previousPageData.hasNext) return null; // 第一页 page1 return /api/items?page${pageIndex 1}limit20; }; const { data, size, setSize, isLoading } useSWRInfinite(getKey, fetcher); const items data ? data.flatMap(page page.items) : []; const isLoadingMore isLoading || (size 0 data typeof data[size - 1] undefined); const isEmpty data?.[0]?.items?.length 0; const isReachingEnd isEmpty || (data data[data.length - 1]?.items?.length 20); return ( div {items.map(item ( div key{item.id}{item.name}/div ))} {!isReachingEnd ( button onClick{() setSize(size 1)} disabled{isLoadingMore} {isLoadingMore ? 加载中... : 加载更多} /button )} /div ); }3.7 数据变更import useSWR, { mutate } from swr; function CreatePostSWR() { const { data: posts, mutate: mutatePosts } useSWR(/api/posts, fetcher); const createPost async (newPost) { // 乐观更新 const optimisticPosts [...posts, newPost]; mutatePosts(optimisticPosts, false); // 不重新验证 try { const response await fetch(/api/posts, { method: POST, body: JSON.stringify(newPost), }); const data await response.json(); // 使用服务器返回的数据更新 mutatePosts([...posts, data]); } catch (error) { // 发生错误时回滚 mutatePosts(posts); console.error(error); } }; return button onClick{() createPost({ title: 新文章 })} 创建文章 /button; } // 全局 mutate function UpdateUser() { const updateUser async (userId, userData) { // 更新特定 key 的数据 mutate(/api/users/${userId}, async () { const response await fetch(/api/users/${userId}, { method: PUT, body: JSON.stringify(userData), }); return response.json(); }); }; return button onClick{() updateUser(1, { name: New Name })} 更新用户 /button; }4. React Query vs SWR 对比4.1 特性对比特性React QuerySWR学习曲线中等平缓API 设计功能丰富选项多简洁易上手DevTools优秀的开发者工具基础版TypeScript极好极好体积~13KB~5KB无限查询useInfiniteQueryuseSWRInfinite并行查询useQueries多个 useSWR依赖查询enabled 选项key 为 null乐观更新内置支持需要手动实现GC 时间可配置 gcTime无内置 GCReact 19 支持完全支持完全支持4.2 选择建议// 选择 React Query 如果 // - 需要复杂的数据同步逻辑 // - 需要强大的开发者工具 // - 需要细粒度的缓存控制 // - 项目已经使用 TanStack 生态 // 选择 SWR 如果 // - 追求简洁和轻量 // - 只需要基础的缓存和重新验证 // - 项目使用 Next.jsSWR 由 Vercel 开发 // - 对包体积有严格要求5. 高级模式5.1 预取数据// React Query const queryClient useQueryClient(); // 鼠标悬停时预取 function UserLink({ userId }) { const prefetchUser () { queryClient.prefetchQuery({ queryKey: [user, userId], queryFn: () fetchUser(userId), }); }; return ( Link to{/users/${userId}} onMouseEnter{prefetchUser} 查看用户 /Link ); }5.2 SSR/SSG 数据获取// Next.js SWR function Profile({ fallbackData }) { const { data } useSWR(/api/user, fetcher, { fallbackData }); return div{data.name}/div; } // React Query Next.js import { dehydrate, HydrationBoundary } from tanstack/react-query; export async function getServerSideProps() { const queryClient new QueryClient(); await queryClient.prefetchQuery({ queryKey: [user], queryFn: fetchUser, }); return { props: { dehydratedState: dehydrate(queryClient), }, }; }5.3 轮询 / 实时数据// React Query 轮询 const { data } useQuery({ queryKey: [realtime-data], queryFn: fetchData, refetchInterval: 5000, // 每5秒重新获取 refetchIntervalInBackground: true, // 后台也轮询 }); // SWR 轮询 const { data } useSWR(/api/realtime, fetcher, { refreshInterval: 5000, refreshWhenHidden: true, });6. 性能优化技巧6.1 缓存策略// React Query 缓存策略 const queryClient new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, // 5分钟内不重新获取 gcTime: 10 * 60 * 1000, // 10分钟后垃圾回收 refetchOnWindowFocus: false, // 不自动重新获取 }, }, }); // 针对特定查询 const { data } useQuery({ queryKey: [static-data], queryFn: fetchStaticData, staleTime: Infinity, // 永不过期 gcTime: Infinity, // 永不清理 });7. 完整示例任务管理应用import { useQuery, useMutation, useQueryClient } from tanstack/react-query; import { useState } from react; // API 函数 const api { fetchTasks: () fetch(/api/tasks).then(res res.json()), createTask: (task) fetch(/api/tasks, { method: POST, body: JSON.stringify(task), headers: { Content-Type: application/json }, }).then(res res.json()), updateTask: ({ id, updates }) fetch(/api/tasks/${id}, { method: PATCH, body: JSON.stringify(updates), headers: { Content-Type: application/json }, }).then(res res.json()), deleteTask: (id) fetch(/api/tasks/${id}, { method: DELETE }), }; function TaskManager() { const [filter, setFilter] useState(all); const queryClient useQueryClient(); // 获取任务列表 const { data: tasks [], isLoading } useQuery({ queryKey: [tasks], queryFn: api.fetchTasks, }); // 创建任务 const createMutation useMutation({ mutationFn: api.createTask, onSuccess: () { queryClient.invalidateQueries({ queryKey: [tasks] }); }, }); // 更新任务 const updateMutation useMutation({ mutationFn: api.updateTask, onMutate: async ({ id, updates }) { await queryClient.cancelQueries({ queryKey: [tasks] }); const previousTasks queryClient.getQueryData([tasks]); queryClient.setQueryData([tasks], (old) old.map(task task.id id ? { ...task, ...updates } : task) ); return { previousTasks }; }, onError: (err, variables, context) { queryClient.setQueryData([tasks], context.previousTasks); }, onSettled: () { queryClient.invalidateQueries({ queryKey: [tasks] }); }, }); // 删除任务 const deleteMutation useMutation({ mutationFn: api.deleteTask, onSuccess: () { queryClient.invalidateQueries({ queryKey: [tasks] }); }, }); const filteredTasks tasks.filter(task { if (filter active) return !task.completed; if (filter completed) return task.completed; return true; }); const handleAddTask (title) { createMutation.mutate({ title, completed: false }); }; const handleToggleTask (id, completed) { updateMutation.mutate({ id, updates: { completed: !completed } }); }; const handleDeleteTask (id) { deleteMutation.mutate(id); }; if (isLoading) return div加载中.../div; return ( div h1任务管理/h1 AddTaskForm onAdd{handleAddTask} / div button onClick{() setFilter(all)}全部/button button onClick{() setFilter(active)}未完成/button button onClick{() setFilter(completed)}已完成/button /div TaskList tasks{filteredTasks} onToggle{handleToggleTask} onDelete{handleDeleteTask} / {createMutation.isPending div添加中.../div} /div ); } function AddTaskForm({ onAdd }) { const [title, setTitle] useState(); const handleSubmit (e) { e.preventDefault(); if (title.trim()) { onAdd(title); setTitle(); } }; return ( form onSubmit{handleSubmit} input typetext value{title} onChange{(e) setTitle(e.target.value)} placeholder添加新任务... / button typesubmit添加/button /form ); } function TaskList({ tasks, onToggle, onDelete }) { return ( ul {tasks.map(task ( li key{task.id} input typecheckbox checked{task.completed} onChange{() onToggle(task.id, task.completed)} / span style{{ textDecoration: task.completed ? line-through : none }} {task.title} /span button onClick{() onDelete(task.id)}删除/button /li ))} /ul ); } export default TaskManager;8. 总结核心要点要点说明核心价值自动管理服务端状态减少样板代码主要特性缓存、重试、后台更新、乐观更新React Query功能全面适合复杂场景SWR轻量简洁适合中小项目记忆口诀服务状态管理难React Query SWR 来帮忙缓存重试自动做加载错误不用忙乐观更新体验好后台刷新数据强9. 相关资源React Query 官方文档SWR 官方文档React Query DevTools